1 // --------------------------------------------------------------------------------------------------------------------
2 // <copyright file=
"NetworkingPeer.cs" company="Exit Games GmbH">
3 // Part of: Photon Unity Networking (PUN)
4 // </copyright>
5 // --------------------------------------------------------------------------------------------------------------------

6
7 using
ExitGames.Client.Photon;
8 using
ExitGames.Client.Photon.Lite;
9 using
System;
10 using
System.Collections;
11 using
System.Collections.Generic;
12 using
System.Reflection;
13 using
UnityEngine;
14 using
Hashtable = ExitGames.Client.Photon.Hashtable;
15
16 ///
<summary>
17 ///
Implements Photon LoadBalancing used in PUN.
18 ///
This class is used internally by PhotonNetwork and not intended as public API.
19 ///
</summary>
20 internal
class NetworkingPeer : LoadbalancingPeer, IPhotonPeerListener
21 {

22     ///
<summary>The GameVersion as set in the Connect-methods.</summary>
23     
protected internal string mAppVersion;
24
25     ///
<summary>Combination of GameVersion+"_"+PunVersion. Separates players per app by version.</summary>
26     
protected internal string mAppVersionPun
27     {
28         
get { return string.Format("{0}_{1}", mAppVersion, PhotonNetwork.versionPUN); }
29     }

30
31     ///
<summary>Contains the AppId for the Photon Cloud (ignored by Photon Servers).</summary>
32     
protected internal string mAppId;
33
34     ///
<summary>
35     ///
A user's authentication values used during connect for Custom Authentication with Photon (and a custom service/community).
36     ///
Set these before calling Connect if you want custom authentication.
37     ///
</summary>
38     
public AuthenticationValues CustomAuthenticationValues { get; set; }
39
40     
public string MasterServerAddress { get; protected internal set; }
41
42     
private string playername = "";
43
44     
private IPhotonPeerListener externalListener;
45
46     
private JoinType mLastJoinType;
47
48     
private bool mPlayernameHasToBeUpdated;
49
50     
public string PlayerName
51     {
52         
get
53         {
54             
return this.playername;
55         }
56
57         
set
58         {
59             
if (string.IsNullOrEmpty(value) || value.Equals(this.playername))
60             {
61                 
return;
62             }
63
64             
if (this.mLocalActor != null)
65             {
66                 
this.mLocalActor.name = value;
67             }
68
69             
this.playername = value;
70             
if (this.mCurrentGame != null)
71             {
72                 
// Only when in a room
73                 
this.SendPlayerName();
74             }
75         }
76     }
77
78     
public PeerState State { get; internal set; }
79
80     
// "public" access to the current game - is null unless a room is joined on a gameserver
81     
public Room mCurrentGame
82     {
83         
get
84         {
85             
if (this.mRoomToGetInto != null && this.mRoomToGetInto.isLocalClientInside)
86             {
87                 
return this.mRoomToGetInto;
88             }
89
90             
return null;
91         }
92     }

93
94     ///
<summary>
95     ///
keeps the custom properties, gameServer address and anything else about the room we want to get into
96     ///
</summary>
97     
internal Room mRoomToGetInto { get; set; }
98     
internal RoomOptions mRoomOptionsForCreate { get; set; }
99     
internal TypedLobby mRoomToEnterLobby { get; set; }
100
101     
public Dictionary<int, PhotonPlayer> mActors = new Dictionary<int, PhotonPlayer>();
102
103     
public PhotonPlayer[] mOtherPlayerListCopy = new PhotonPlayer[0];
104     
public PhotonPlayer[] mPlayerListCopy = new PhotonPlayer[0];
105
106     
public PhotonPlayer mLocalActor { get; internal set; }
107
108     
public PhotonPlayer mMasterClient = null;
109
110     
public bool hasSwitchedMC = false;
111
112     
public string mGameserver { get; internal set; }
113
114     
public bool requestSecurity = true;
115
116     
private Dictionary<Type, List<MethodInfo>> monoRPCMethodsCache = new Dictionary<Type, List<MethodInfo>>();
117
118     
public static bool UsePrefabCache = true;
119
120     
public static Dictionary<string, GameObject> PrefabCache = new Dictionary<string, GameObject>();
121
122     
public Dictionary<string, RoomInfo> mGameList = new Dictionary<string, RoomInfo>();
123     
public RoomInfo[] mGameListCopy = new RoomInfo[0];
124
125     
public TypedLobby lobby { get; set; }
126
127     
public bool insideLobby = false;
128
129     ///
<summary>Stat value: Count of players on Master (looking for rooms)</summary>
130     
public int mPlayersOnMasterCount { get; internal set; }
131
132     ///
<summary>Stat value: Count of Rooms</summary>
133     
public int mGameCount { get; internal set; }
134
135     ///
<summary>Stat value: Count of Players in rooms</summary>
136     
public int mPlayersInRoomsCount { get; internal set; }
137     
138     
private HashSet<int> allowedReceivingGroups = new HashSet<int>();
139
140     
private HashSet<int> blockSendingGroups = new HashSet<int>();
141
142     
internal protected Dictionary<int, PhotonView> photonViewList = new Dictionary<int, PhotonView>(); //TODO: make private again
143
144     
private readonly Dictionary<int, Hashtable> dataPerGroupReliable = new Dictionary<int, Hashtable>(); // only used in RunViewUpdate()
145     
private readonly Dictionary<int, Hashtable> dataPerGroupUnreliable = new Dictionary<int, Hashtable>(); // only used in RunViewUpdate()
146
147     
internal protected short currentLevelPrefix = 0;
148
149     
private readonly Dictionary<string, int> rpcShortcuts; // lookup "table" for the index (shortcut) of an RPC name
150
151     ///
<summary>The server this client is currently connected or connecting to.</summary>
152     
internal protected ServerConnection server { get; private set; }
153
154     
public bool IsInitialConnect = false;
155
156     ///
<summary>True if this client uses a NameServer to get the Master Server address.</summary>
157     
public bool IsUsingNameServer { get; protected internal set; }
158
159     ///
<summary>Name Server Address that this client uses. You can use the default values and usually won't have to set this value.</summary>
160     
public string NameServerAddress = "ns.exitgamescloud.com";
161     
public string NameServerAddressHttp = "http://ns.exitgamescloud.com:80/photon/n";
162
163     
private static readonly Dictionary<ConnectionProtocol, int> ProtocolToNameServerPort = new Dictionary<ConnectionProtocol, int>() { {ConnectionProtocol.Udp, 5058}, {ConnectionProtocol.Tcp, 4533} }; //, { ConnectionProtocol.RHttp, 6063 } };
164
165     ///
<summary>A list of region names for the Photon Cloud. Set by the result of OpGetRegions().</summary>
166     ///
<remarks>Put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available.</remarks>
167     
public List<Region> AvailableRegions { get; protected internal set; }
168
169     ///
<summary>The cloud region this client connects to. Set by ConnectToRegionMaster().</summary>
170     
public CloudRegionCode CloudRegion { get; protected internal set; }
171
172     ///
<summary>Internally used to check if a "Secret" is available to use. Sent by Photon Cloud servers, it simplifies authentication when switching servers.</summary>
173     
public bool IsAuthorizeSecretAvailable
174     {
175         
get
176         {
177             
// TODO: comment in the code again, when the new auth-workflow is available in the Cloud
178             
return false; // this.CustomAuthenticationValues != null && !String.IsNullOrEmpty(this.CustomAuthenticationValues.Secret);
179         }
180     }

181
182     ///
<summary>Internally used to trigger OpAuthenticate when encryption was established after a connect.</summary>
183     
private bool didAuthenticate;
184
185     
public NetworkingPeer(IPhotonPeerListener listener, string playername, ConnectionProtocol connectionProtocol) : base(listener, connectionProtocol)
186     {
187         #
if !UNITY_EDITOR && (UNITY_WINRT)
188         
// this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)
189         Debug.LogWarning(
"Using PingWindowsStore");
190         PhotonHandler.PingImplementation =
typeof(PingWindowsStore); // but for ping, we have to set the implementation explicitly to Win 8 Store/Phone
191         #endif
192
193         #pragma warning disable
0162 // the library variant defines if we should use PUN's SocketUdp variant (at all)
194         
if (PhotonPeer.NoSocket)
195         {
196             #
if !UNITY_EDITOR && (UNITY_PS3 || UNITY_ANDROID)
197             Debug.Log(
"Using class SocketUdpNativeDynamic");
198             
this.SocketImplementation = typeof(SocketUdpNativeDynamic);
199             PhotonHandler.PingImplementation =
typeof(PingNativeDynamic);
200             #elif !UNITY_EDITOR && UNITY_IPHONE
201             Debug.Log(
"Using class SocketUdpNativeStatic");
202             
this.SocketImplementation = typeof(SocketUdpNativeStatic);
203             PhotonHandler.PingImplementation =
typeof(PingNativeStatic);
204             #elif !UNITY_EDITOR && (UNITY_WINRT)
205             
// this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)
206             #
else
207             
this.SocketImplementation = typeof (SocketUdp);
208             PhotonHandler.PingImplementation =
typeof(PingMonoEditor);
209             #endif
210
211             
if (this.SocketImplementation == null)
212             {
213                 Debug.Log(
"No socket implementation set for 'NoSocket' assembly. Please contact Exit Games.");
214             }
215         }
216         #pragma warning restore
0162
217
218         
if (PhotonHandler.PingImplementation == null)
219         {
220             PhotonHandler.PingImplementation =
typeof(PingMono);
221         }
222
223         
this.Listener = this;
224         
this.lobby = TypedLobby.Default;
225         
this.LimitOfUnreliableCommands = 40;
226
227         
// don't set the field directly! the listener is passed on to other classes, which get updated by the property set method
228         
this.externalListener = listener;
229         
this.PlayerName = playername;
230         
this.mLocalActor = new PhotonPlayer(true, -1, this.playername);
231         
this.AddNewPlayer(this.mLocalActor.ID, this.mLocalActor);
232
233         
// RPC shortcut lookup creation (from list of RPCs, which is updated by Editor scripts)
234         rpcShortcuts =
new Dictionary<string, int>(PhotonNetwork.PhotonServerSettings.RpcList.Count);
235         
for (int index = 0; index < PhotonNetwork.PhotonServerSettings.RpcList.Count; index++)
236         {
237             
var name = PhotonNetwork.PhotonServerSettings.RpcList[index];
238             rpcShortcuts[name] = index;
239         }
240
241         
this.State = global::PeerState.PeerCreated;
242     }
243
244     
#region Operations and Connection Methods
245
246
247     
public override bool Connect(string serverAddress, string applicationName)
248     {
249         Debug.LogError(
"Avoid using this directly. Thanks.");
250         
return false;
251     }
252
253     
public bool Connect(string serverAddress, ServerConnection type)
254     {
255         
if (PhotonHandler.AppQuits)
256         {
257             Debug.LogWarning(
"Ignoring Connect() because app gets closed. If this is an error, check PhotonHandler.AppQuits.");
258             
return false;
259         }
260
261         
if (PhotonNetwork.connectionStateDetailed == global::PeerState.Disconnecting)
262         {
263             Debug.LogError(
"Connect() failed. Can't connect while disconnecting (still). Current state: " + PhotonNetwork.connectionStateDetailed);
264             
return false;
265         }
266
267         
// connect might fail, if the DNS name can't be resolved or if no network connection is available
268         
bool connecting = base.Connect(serverAddress, "");
269         
if (connecting)
270         {
271             
switch (type)
272             {
273                 
case ServerConnection.NameServer:
274                     State =
global::PeerState.ConnectingToNameServer;
275                     
break;
276                 
case ServerConnection.MasterServer:
277                     State =
global::PeerState.ConnectingToMasterserver;
278                     
break;
279                 
case ServerConnection.GameServer:
280                     State =
global::PeerState.ConnectingToGameserver;
281                     
break;
282             }
283         }
284
285         
return connecting;
286     }

287
288
289     ///
<summary>
290     ///
Connects to the NameServer for Photon Cloud, where a region and server list can be obtained.
291     ///
</summary>
292     ///
<see cref="OpGetRegions"/>
293     ///
<returns>If the workflow was started or failed right away.</returns>
294     
public bool ConnectToNameServer()
295     {
296         
if (PhotonHandler.AppQuits)
297         {
298             Debug.LogWarning(
"Ignoring Connect() because app gets closed. If this is an error, check PhotonHandler.AppQuits.");
299             
return false;
300         }
301
302         IsUsingNameServer =
true;
303         
this.CloudRegion = CloudRegionCode.none;
304
305         
if (this.State == global::PeerState.ConnectedToNameServer)
306         {
307             
return true;
308         }
309
310         #
if RHTTP
311         
string address = (this.UsedProtocol == ConnectionProtocol.RHttp) ? this.NameServerAddressHttp : this.NameServerAddress;
312         #
else
313         
string address = this.NameServerAddress;
314         #endif
315
316         
if (!address.Contains(":"))
317         {
318             
int port = 0;
319             ProtocolToNameServerPort.TryGetValue(
this.UsedProtocol, out port);
320             address =
string.Format("{0}:{1}", address, port);
321             Debug.Log(
"Server to connect to: " + address + " settings protocol: " + PhotonNetwork.PhotonServerSettings.Protocol);
322         }
323         
if (!base.Connect(address, "ns"))
324         {
325             
return false;
326         }
327
328         
this.State = global::PeerState.ConnectingToNameServer;
329         
return true;
330     }

331
332     ///
<summary>
333     ///
Connects you to a specific region's Master Server, using the Name Server to find the IP.
334     ///
</summary>
335     ///
<returns>If the operation could be sent. If false, no operation was sent.</returns>
336     
public bool ConnectToRegionMaster(CloudRegionCode region)
337     {
338         
if (PhotonHandler.AppQuits)
339         {
340             Debug.LogWarning(
"Ignoring Connect() because app gets closed. If this is an error, check PhotonHandler.AppQuits.");
341             
return false;
342         }
343
344         IsUsingNameServer =
true;
345         
this.CloudRegion = region;
346
347         
if (this.State == global::PeerState.ConnectedToNameServer)
348         {
349             
return this.OpAuthenticate(this.mAppId, this.mAppVersionPun, this.PlayerName, this.CustomAuthenticationValues, region.ToString());
350         }
351
352         #
if RHTTP
353         
string address = (this.UsedProtocol == ConnectionProtocol.RHttp) ? this.NameServerAddressHttp : this.NameServerAddress;
354         #
else
355         
string address = this.NameServerAddress;
356         #endif
357
358         
if (!address.Contains(":"))
359         {
360             
int port = 0;
361             ProtocolToNameServerPort.TryGetValue(
this.UsedProtocol, out port);
362             address =
string.Format("{0}:{1}", address, port);
363             
//Debug.Log("Server to connect to: "+ address + " settings protocol: " + PhotonNetwork.PhotonServerSettings.Protocol);
364         }
365         
if (!base.Connect(address, "ns"))
366         {
367             
return false;
368         }
369
370         
this.State = global::PeerState.ConnectingToNameServer;
371         
return true;
372     }

373
374     ///
<summary>
375     ///
While on the NameServer, this gets you the list of regional servers (short names and their IPs to ping them).
376     ///
</summary>
377     ///
<returns>If the operation could be sent. If false, no operation was sent (e.g. while not connected to the NameServer).</returns>
378     
public bool GetRegions()
379     {
380         
if (this.server != ServerConnection.NameServer)
381         {
382             
return false;
383         }
384
385         
bool sent = this.OpGetRegions(this.mAppId);
386         
if (sent)
387         {
388             
this.AvailableRegions = null;
389         }
390
391         
return sent;
392     }

393
394     ///
<summary>
395     ///
Complete disconnect from photon (and the open master OR game server)
396     ///
</summary>
397     
public override void Disconnect()
398     {
399         
if (this.PeerState == PeerStateValue.Disconnected)
400         {
401             
if (!PhotonHandler.AppQuits)
402             {
403                 Debug.LogWarning(
string.Format("Can't execute Disconnect() while not connected. Nothing changed. State: {0}", this.State));
404             }
405             
return;
406         }
407
408         
this.State = global::PeerState.Disconnecting;
409         
base.Disconnect();
410
411         
//this.LeftRoomCleanup();
412         
//this.LeftLobbyCleanup();
413     }

414
415
416     ///
<summary>
417     ///
Internally used only. Triggers OnStateChange with "Disconnect" in next dispatch which is the signal to re-connect (if at all).
418     ///
</summary>
419     
private void DisconnectToReconnect()
420     {
421         
switch (this.server)
422         {
423             
case ServerConnection.NameServer:
424                 
this.State = global::PeerState.DisconnectingFromNameServer;
425                 
base.Disconnect();
426                 
break;
427             
case ServerConnection.MasterServer:
428                 
this.State = global::PeerState.DisconnectingFromMasterserver;
429                 
base.Disconnect();
430                 
//LeftLobbyCleanup();
431                 
break;
432             
case ServerConnection.GameServer:
433                 
this.State = global::PeerState.DisconnectingFromGameserver;
434                 
base.Disconnect();
435                 
//this.LeftRoomCleanup();
436                 
break;
437         }
438     }

439
440     ///
<summary>
441     ///
Called at disconnect/leavelobby etc. This CAN also be called when we are not in a lobby (e.g. disconnect from room)
442     ///
</summary>
443     ///
<remarks>Calls callback method OnLeftLobby if this client was in a lobby initially. Clears the lobby's game lists.</remarks>
444     
private void LeftLobbyCleanup()
445     {
446         
this.mGameList = new Dictionary<string, RoomInfo>();
447         
this.mGameListCopy = new RoomInfo[0];
448
449         
if (insideLobby)
450         {
451             
this.insideLobby = false;
452             SendMonoMessage(PhotonNetworkingMessage.OnLeftLobby);
453         }
454     }

455
456     ///
<summary>
457     ///
Called when "this client" left a room to clean up.
458     ///
</summary>
459     
private void LeftRoomCleanup()
460     {
461         
bool wasInRoom = mRoomToGetInto != null;
462         
// when leaving a room, we clean up depending on that room's settings.
463         
bool autoCleanupSettingOfRoom = (this.mRoomToGetInto != null) ? this.mRoomToGetInto.autoCleanUp : PhotonNetwork.autoCleanUpPlayerObjects;
464
465         
this.hasSwitchedMC = false;
466         
this.mRoomToGetInto = null;
467         
this.mActors = new Dictionary<int, PhotonPlayer>();
468         
this.mPlayerListCopy = new PhotonPlayer[0];
469         
this.mOtherPlayerListCopy = new PhotonPlayer[0];
470         
this.mMasterClient = null;
471         
this.allowedReceivingGroups = new HashSet<int>();
472         
this.blockSendingGroups = new HashSet<int>();
473         
this.mGameList = new Dictionary<string, RoomInfo>();
474         
this.mGameListCopy = new RoomInfo[0];
475         
this.isFetchingFriends = false;
476
477         
this.ChangeLocalID(-1);
478
479         
// Cleanup all network objects (all spawned PhotonViews, local and remote)
480         
if (autoCleanupSettingOfRoom)
481         {
482             
this.LocalCleanupAnythingInstantiated(true);
483             PhotonNetwork.manuallyAllocatedViewIds =
new List<int>(); // filled and easier to replace completely
484         }
485
486         
if (wasInRoom)
487         {
488             SendMonoMessage(PhotonNetworkingMessage.OnLeftRoom);
489         }
490     }

491
492     ///
<summary>
493     ///
Cleans up anything that was instantiated in-game (not loaded with the scene).
494     ///
</summary>
495     
protected internal void LocalCleanupAnythingInstantiated(bool destroyInstantiatedGameObjects)
496     {
497         
if (tempInstantiationData.Count > 0)
498         {
499             Debug.LogWarning(
"It seems some instantiation is not completed, as instantiation data is used. You should make sure instantiations are paused when calling this method. Cleaning now, despite this.");
500         }
501
502         
// Destroy GO's (if we should)
503         
if (destroyInstantiatedGameObjects)
504         {
505             
// Fill list with Instantiated objects
506             HashSet<GameObject> instantiatedGos =
new HashSet<GameObject>();
507             
foreach (PhotonView view in this.photonViewList.Values)
508             {
509                 
if (view.isRuntimeInstantiated)
510                 {
511                     instantiatedGos.Add(view.gameObject);
// HashSet keeps each object only once
512                 }
513             }
514             
515             
foreach (GameObject go in instantiatedGos)
516             {
517                 
this.RemoveInstantiatedGO(go, true);
518             }
519         }
520
521         
// photonViewList is cleared of anything instantiated (so scene items are left inside)
522         
// any other lists can be
523         
this.tempInstantiationData.Clear(); // should be empty but to be safe we clear (no new list needed)
524         PhotonNetwork.lastUsedViewSubId =
0;
525         PhotonNetwork.lastUsedViewSubIdStatic =
0;
526     }
527
528     
// gameID can be null (optional). The server assigns a unique name if no name is set
529
530     
// joins a room and sets your current username as custom actorproperty (will broadcast that)
531
532     
#endregion
533
534     
#region Helpers
535
536     
private void ReadoutProperties(Hashtable gameProperties, Hashtable pActorProperties, int targetActorNr)
537     {
538         
// Debug.LogWarning("ReadoutProperties gameProperties: " + gameProperties.ToStringFull() + " pActorProperties: " + pActorProperties.ToStringFull() + " targetActorNr: " + targetActorNr);
539         
// read game properties and cache them locally
540         
if (this.mCurrentGame != null && gameProperties != null)
541         {
542             
this.mCurrentGame.CacheProperties(gameProperties);
543             SendMonoMessage(PhotonNetworkingMessage.OnPhotonCustomRoomPropertiesChanged, gameProperties);
544             
if (PhotonNetwork.automaticallySyncScene)
545             {
546                 
this.LoadLevelIfSynced(); // will load new scene if sceneName was changed
547             }
548         }
549
550         
if (pActorProperties != null && pActorProperties.Count > 0)
551         {
552             
if (targetActorNr > 0)
553             {
554                 
// we have a single entry in the pActorProperties with one
555                 
// user's name
556                 
// targets MUST exist before you set properties
557                 PhotonPlayer target =
this.GetPlayerWithID(targetActorNr);
558                 
if (target != null)
559                 {
560                     Hashtable props =
this.GetActorPropertiesForActorNr(pActorProperties, targetActorNr);
561                     target.InternalCacheProperties(props);
562                     SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, target, props);
563                 }
564             }
565             
else
566             {
567                 
// in this case, we've got a key-value pair per actor (each
568                 
// value is a hashtable with the actor's properties then)
569                 
int actorNr;
570                 Hashtable props;
571                 
string newName;
572                 PhotonPlayer target;
573
574                 
foreach (object key in pActorProperties.Keys)
575                 {
576                     actorNr = (
int)key;
577                     props = (Hashtable)pActorProperties[key];
578                     newName = (
string)props[ActorProperties.PlayerName];
579
580                     target =
this.GetPlayerWithID(actorNr);
581                     
if (target == null)
582                     {
583                         target =
new PhotonPlayer(false, actorNr, newName);
584                         
this.AddNewPlayer(actorNr, target);
585                     }
586
587                     target.InternalCacheProperties(props);
588                     SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, target, props);
589                 }
590             }
591         }
592     }
593
594     
private void AddNewPlayer(int ID, PhotonPlayer player)
595     {
596         
if (!this.mActors.ContainsKey(ID))
597         {
598             
this.mActors[ID] = player;
599             RebuildPlayerListCopies();
600         }
601         
else
602         {
603             Debug.LogError(
"Adding player twice: " + ID);
604         }
605     }
606
607     
void RemovePlayer(int ID, PhotonPlayer player)
608     {
609         
this.mActors.Remove(ID);
610         
if (!player.isLocal)
611         {
612             RebuildPlayerListCopies();
613         }
614     }
615
616     
void RebuildPlayerListCopies()
617     {
618         
this.mPlayerListCopy = new PhotonPlayer[this.mActors.Count];
619         
this.mActors.Values.CopyTo(this.mPlayerListCopy, 0);
620
621         List<PhotonPlayer> otherP =
new List<PhotonPlayer>();
622         
foreach (PhotonPlayer player in this.mPlayerListCopy)
623         {
624             
if (!player.isLocal)
625             {
626                 otherP.Add(player);
627             }
628         }
629
630         
this.mOtherPlayerListCopy = otherP.ToArray();
631     }

632
633     ///
<summary>
634     ///
Resets the PhotonView "lastOnSerializeDataSent" so that "OnReliable" synched PhotonViews send a complete state to new clients (if the state doesnt change, no messages would be send otherwise!).
635     ///
Note that due to this reset, ALL other players will receive the full OnSerialize.
636     ///
</summary>
637     
private void ResetPhotonViewsOnSerialize()
638     {
639         
foreach (PhotonView photonView in this.photonViewList.Values)
640         {
641             photonView.lastOnSerializeDataSent =
null;
642         }
643     }

644
645     ///
<summary>
646     ///
Called when the event Leave (of some other player) arrived.
647     ///
Cleans game objects, views locally. The master will also clean the
648     ///
</summary>
649     ///
<param name="actorID">ID of player who left.</param>
650     
private void HandleEventLeave(int actorID)
651     {
652         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
653             Debug.Log(
"HandleEventLeave for player ID: " + actorID);
654
655
656         
// actorNr is fetched out of event above
657         
if (actorID < 0 || !this.mActors.ContainsKey(actorID))
658         {
659             Debug.LogError(String.Format(
"Received event Leave for unknown player ID: {0}", actorID));
660             
return;
661         }
662
663         PhotonPlayer player =
this.GetPlayerWithID(actorID);
664         
if (player == null)
665         {
666             Debug.LogError(
"HandleEventLeave for player ID: " + actorID + " has no PhotonPlayer!");
667         }
668
669         
// having a new master before calling destroy for the leaving player is important!
670         
// so we elect a new masterclient and ignore the leaving player (who is still in playerlists).
671         
this.CheckMasterClient(actorID);
672
673
674         
// destroy objects & buffered messages
675         
if (this.mCurrentGame != null && this.mCurrentGame.autoCleanUp)
676         {
677             
this.DestroyPlayerObjects(actorID, true);
678         }
679
680         RemovePlayer(actorID, player);
681
682         
// finally, send notification (the playerList and masterclient are now updated)
683         SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerDisconnected, player);
684     }

685
686     ///
<summary>Picks the new master client from player list, if the current Master is leaving (leavingPlayerId) or if no master was assigned so far.</summary>
687     ///
<param name="leavingPlayerId">
688     ///
The ignored player is the one who's leaving and should not become master (again). Pass -1 to select any player from the list.
689     ///
</param>
690     
private void CheckMasterClient(int leavingPlayerId)
691     {
692         
bool currentMasterIsLeaving = this.mMasterClient != null && this.mMasterClient.ID == leavingPlayerId;
693         
bool someoneIsLeaving = leavingPlayerId > 0;
694
695         
// return early if SOME player (leavingId > 0) is leaving AND it's NOT the current master
696         
if (someoneIsLeaving && !currentMasterIsLeaving)
697         {
698             
return;
699         }
700
701         
// picking the player with lowest ID (longest in game).
702         
if (this.mActors.Count <= 1)
703         {
704             
this.mMasterClient = this.mLocalActor;
705         }
706         
else
707         {
708             
// keys in mActors are their actorNumbers
709             
int lowestActorNumber = Int32.MaxValue;
710             
foreach (int key in this.mActors.Keys)
711             {
712                 
if (key < lowestActorNumber && key != leavingPlayerId)
713                 {
714                     lowestActorNumber = key;
715                 }
716             }
717
718             
this.mMasterClient = this.mActors[lowestActorNumber];
719         }
720
721         
// make a callback ONLY when a player/Master left
722         
if (someoneIsLeaving)
723         {
724             SendMonoMessage(PhotonNetworkingMessage.OnMasterClientSwitched,
this.mMasterClient);
725         }
726     }

727
728     ///
<summary>
729     ///
Returns the lowest player.ID - used for Master Client picking.
730     ///
</summary>
731     ///
<remarks></remarks>
732     
private static int ReturnLowestPlayerId(PhotonPlayer[] players, int playerIdToIgnore)
733     {
734         
if (players == null || players.Length == 0)
735         {
736             
return -1;
737         }
738
739         
int lowestActorNumber = Int32.MaxValue;
740         
for (int i = 0; i < players.Length; i++)
741         {
742             PhotonPlayer photonPlayer = players[i];
743             
if (photonPlayer.ID == playerIdToIgnore)
744             {
745                 
continue;
746             }
747
748             
if (photonPlayer.ID < lowestActorNumber)
749             {
750                 lowestActorNumber = photonPlayer.ID;
751             }
752         }
753
754         
return lowestActorNumber;
755     }
756
757     
internal protected bool SetMasterClient(int playerId, bool sync)
758     {
759         
bool masterReplaced = this.mMasterClient != null && this.mMasterClient.ID != playerId;
760         
if (!masterReplaced || !this.mActors.ContainsKey(playerId))
761         {
762             
return false;
763         }
764
765         
if (sync)
766         {
767             
bool sent = this.OpRaiseEvent(PunEvent.AssignMaster, new Hashtable() { { (byte)1, playerId } }, true, null);
768             
if (!sent)
769             {
770                 
return false;
771             }
772         }
773
774         
this.hasSwitchedMC = true;
775         
this.mMasterClient = this.mActors[playerId];
776         SendMonoMessage(PhotonNetworkingMessage.OnMasterClientSwitched,
this.mMasterClient); // we only callback when an actual change is done
777         
return true;
778     }
779
780     
private Hashtable GetActorPropertiesForActorNr(Hashtable actorProperties, int actorNr)
781     {
782         
if (actorProperties.ContainsKey(actorNr))
783         {
784             
return (Hashtable)actorProperties[actorNr];
785         }
786
787         
return actorProperties;
788     }
789
790     
private PhotonPlayer GetPlayerWithID(int number)
791     {
792         
if (this.mActors != null && this.mActors.ContainsKey(number))
793         {
794             
return this.mActors[number];
795         }
796
797         
return null;
798     }
799
800     
private void SendPlayerName()
801     {
802         
if (this.State == global::PeerState.Joining)
803         {
804             
// this means, the join on the gameServer is sent (with an outdated name). send the new when in game
805             
this.mPlayernameHasToBeUpdated = true;
806             
return;
807         }
808
809         
if (this.mLocalActor != null)
810         {
811             
this.mLocalActor.name = this.PlayerName;
812             Hashtable properties =
new Hashtable();
813             properties[ActorProperties.PlayerName] =
this.PlayerName;
814             
if (this.mLocalActor.ID > 0)
815             {
816                 
this.OpSetPropertiesOfActor(this.mLocalActor.ID, properties, true, (byte)0);
817                 
this.mPlayernameHasToBeUpdated = false;
818             }
819         }
820     }
821
822     
private void GameEnteredOnGameServer(OperationResponse operationResponse)
823     {
824         
if (operationResponse.ReturnCode != 0)
825         {
826             
switch (operationResponse.OperationCode)
827             {
828                 
case OperationCode.CreateGame:
829                     
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
830                     {
831                         Debug.Log(
"Create failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
832                     }
833                     SendMonoMessage(PhotonNetworkingMessage.OnPhotonCreateRoomFailed, operationResponse.ReturnCode, operationResponse.DebugMessage);
834                     
break;
835                 
case OperationCode.JoinGame:
836                     
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
837                     {
838                         Debug.Log(
"Join failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
839                         
if (operationResponse.ReturnCode == ErrorCode.GameDoesNotExist)
840                         {
841                             Debug.Log(
"Most likely the game became empty during the switch to GameServer.");
842                         }
843                     }
844                     SendMonoMessage(PhotonNetworkingMessage.OnPhotonJoinRoomFailed, operationResponse.ReturnCode, operationResponse.DebugMessage);
845                     
break;
846                 
case OperationCode.JoinRandomGame:
847                     
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
848                     {
849                         Debug.Log(
"Join failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
850                         
if (operationResponse.ReturnCode == ErrorCode.GameDoesNotExist)
851                         {
852                             Debug.Log(
"Most likely the game became empty during the switch to GameServer.");
853                         }
854                     }
855                     SendMonoMessage(PhotonNetworkingMessage.OnPhotonRandomJoinFailed, operationResponse.ReturnCode, operationResponse.DebugMessage);
856                     
break;
857             }
858
859             
this.DisconnectToReconnect();
860             
return;
861         }
862
863         
this.State = global::PeerState.Joined;
864         
this.mRoomToGetInto.isLocalClientInside = true;
865
866         Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
867         Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
868         
this.ReadoutProperties(gameProperties, actorProperties, 0);
869
870         
// the local player's actor-properties are not returned in join-result. add this player to the list
871         
int localActorNr = (int)operationResponse[ParameterCode.ActorNr];
872
873         
this.ChangeLocalID(localActorNr);
874         
this.CheckMasterClient(-1);
875
876         
if (this.mPlayernameHasToBeUpdated)
877         {
878             
this.SendPlayerName();
879         }
880
881         
switch (operationResponse.OperationCode)
882         {
883             
case OperationCode.CreateGame:
884                 SendMonoMessage(PhotonNetworkingMessage.OnCreatedRoom);
885                 
break;
886             
case OperationCode.JoinGame:
887             
case OperationCode.JoinRandomGame:
888                 
// the mono message for this is sent at another place
889                 
break;
890         }
891     }
892
893     
private Hashtable GetLocalActorProperties()
894     {
895         
if (PhotonNetwork.player != null)
896         {
897             
return PhotonNetwork.player.allProperties;
898         }
899
900         Hashtable actorProperties =
new Hashtable();
901         actorProperties[ActorProperties.PlayerName] =
this.PlayerName;
902         
return actorProperties;
903     }
904
905     
public void ChangeLocalID(int newID)
906     {
907         
if (this.mLocalActor == null)
908         {
909             Debug.LogWarning(
910                 
string.Format(
911                     
"Local actor is null or not in mActors! mLocalActor: {0} mActors==null: {1} newID: {2}",
912                     
this.mLocalActor,
913                     
this.mActors == null,
914                     newID));
915         }
916
917         
if (this.mActors.ContainsKey(this.mLocalActor.ID))
918         {
919             
this.mActors.Remove(this.mLocalActor.ID);
920         }
921
922         
this.mLocalActor.InternalChangeLocalID(newID);
923         
this.mActors[this.mLocalActor.ID] = this.mLocalActor;
924         
this.RebuildPlayerListCopies();
925     }
926
927     
#endregion
928
929     
#region Operations
930
931     ///
<summary>NetworkingPeer.OpCreateGame</summary>
932     
public bool OpCreateGame(string roomName, RoomOptions roomOptions, TypedLobby typedLobby)
933     {
934         
bool onGameServer = this.server == ServerConnection.GameServer;
935         
if (!onGameServer)
936         {
937             
this.mRoomOptionsForCreate = roomOptions;
938             
this.mRoomToGetInto = new Room(roomName, roomOptions);
939             
this.mRoomToEnterLobby = typedLobby ?? ((this.insideLobby) ? this.lobby : null); // use given lobby, or active lobby (if any active) or none
940         }
941
942         
this.mLastJoinType = JoinType.CreateGame;
943         
return base.OpCreateRoom(roomName, roomOptions, this.mRoomToEnterLobby, this.GetLocalActorProperties(), onGameServer);
944     }

945
946     ///
<summary>NetworkingPeer.OpJoinRoom</summary>
947     
public bool OpJoinRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby, bool createIfNotExists)
948     {
949         
bool onGameServer = this.server == ServerConnection.GameServer;
950         
if (!onGameServer)
951         {
952             
// roomOptions and typedLobby will be null, unless createIfNotExists is true
953             
this.mRoomOptionsForCreate = roomOptions;
954             
this.mRoomToGetInto = new Room(roomName, roomOptions);
955             
this.mRoomToEnterLobby = null;
956             
if (createIfNotExists)
957             {
958                 
this.mRoomToEnterLobby = typedLobby ?? ((this.insideLobby) ? this.lobby : null); // use given lobby, or active lobby (if any active) or none
959             }
960         }
961
962         
this.mLastJoinType = (createIfNotExists) ? JoinType.JoinOrCreateOnDemand : JoinType.JoinGame;
963         
return base.OpJoinRoom(roomName, roomOptions, this.mRoomToEnterLobby, createIfNotExists, this.GetLocalActorProperties(), onGameServer);
964     }

965
966     ///
<summary>NetworkingPeer.OpJoinRandomRoom</summary>
967     ///
<remarks>this override just makes sure we have a mRoomToGetInto, even if it's blank (the properties provided in this method are filters. they are not set when we join the game)</remarks>
968     
public override bool OpJoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers, Hashtable playerProperties, MatchmakingMode matchingType, TypedLobby typedLobby, string sqlLobbyFilter)
969     {
970         
this.mRoomToGetInto = new Room(null, null);
971         
this.mRoomToEnterLobby = null; // join random never stores the lobby. the following join will not affect the room lobby
972         
// if typedLobby is null, the server will automatically use the active lobby or default, which is what we want anyways
973
974         
this.mLastJoinType = JoinType.JoinRandomGame;
975         
return base.OpJoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, playerProperties, matchingType, typedLobby, sqlLobbyFilter);
976     }

977
978     ///
<summary>
979     ///
Operation Leave will exit any current room.
980     ///
</summary>
981     ///
<remarks>
982     ///
This also happens when you disconnect from the server.
983     ///
Disconnect might be a step less if you don't want to create a new room on the same server.
984     ///
</remarks>
985     ///
<returns></returns>
986     
public virtual bool OpLeave()
987     {
988         
if (this.State != global::PeerState.Joined)
989         {
990             Debug.LogWarning(
"Not sending leave operation. State is not 'Joined': " + this.State);
991             
return false;
992         }
993
994         
return this.OpCustom((byte)OperationCode.Leave, null, true, 0);
995     }
996
997     
public override bool OpRaiseEvent(byte eventCode, object customEventContent, bool sendReliable, RaiseEventOptions raiseEventOptions)
998     {
999         
if (PhotonNetwork.offlineMode)
1000         {
1001             
return false;
1002         }
1003
1004         
return base.OpRaiseEvent(eventCode, customEventContent, sendReliable, raiseEventOptions);
1005     }
1006
1007     
#endregion
1008
1009     
#region Implementation of IPhotonPeerListener
1010
1011     
public void DebugReturn(DebugLevel level, string message)
1012     {
1013         
this.externalListener.DebugReturn(level, message);
1014     }
1015
1016     
public void OnOperationResponse(OperationResponse operationResponse)
1017     {
1018         
if (PhotonNetwork.networkingPeer.State == global::PeerState.Disconnecting)
1019         {
1020             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1021             {
1022                 Debug.Log(
"OperationResponse ignored while disconnecting. Code: " + operationResponse.OperationCode);
1023             }
1024             
return;
1025         }
1026
1027         
// extra logging for error debugging (helping developers with a bit of automated analysis)
1028         
if (operationResponse.ReturnCode == 0)
1029         {
1030             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1031                 Debug.Log(operationResponse.ToString());
1032         }
1033         
else
1034         {
1035             
if (operationResponse.ReturnCode == ErrorCode.OperationNotAllowedInCurrentState)
1036             {
1037                 Debug.LogError(
"Operation " + operationResponse.OperationCode + " could not be executed (yet). Wait for state JoinedLobby or ConnectedToMaster and their callbacks before calling operations. WebRPCs need a server-side configuration. Enum OperationCode helps identify the operation.");
1038             }
1039             
else if (operationResponse.ReturnCode == ErrorCode.WebHookCallFailed)
1040             {
1041                 Debug.LogError(
"Operation " + operationResponse.OperationCode + " failed in a server-side plugin. Check the configuration in the Dashboard. Message from server-plugin: " + operationResponse.DebugMessage);
1042             }
1043             
else if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1044             {
1045                 Debug.LogError(
"Operation failed: " + operationResponse.ToStringFull() + " Server: " + this.server);
1046             }
1047         }
1048
1049         
// use the "secret" or "token" whenever we get it. doesn't really matter if it's in AuthResponse.
1050         
if (operationResponse.Parameters.ContainsKey(ParameterCode.Secret))
1051         {
1052             
if (this.CustomAuthenticationValues == null)
1053             {
1054                 
this.CustomAuthenticationValues = new AuthenticationValues();
1055                 
// this.DebugReturn(DebugLevel.ERROR, "Server returned secret. Created CustomAuthenticationValues.");
1056             }
1057
1058             
this.CustomAuthenticationValues.Secret = operationResponse[ParameterCode.Secret] as string;
1059         }
1060
1061         
switch (operationResponse.OperationCode)
1062         {
1063             
case OperationCode.Authenticate:
1064                 {
1065                     
// PeerState oldState = this.State;
1066
1067                     
if (operationResponse.ReturnCode != 0)
1068                     {
1069                         
if (operationResponse.ReturnCode == ErrorCode.InvalidOperationCode)
1070                         {
1071                             Debug.LogError(
string.Format("If you host Photon yourself, make sure to start the 'Instance LoadBalancing' "+ this.ServerAddress));
1072                         }
1073                         
else if (operationResponse.ReturnCode == ErrorCode.InvalidAuthentication)
1074                         {
1075                             Debug.LogError(
string.Format("The appId this client sent is unknown on the server (Cloud). Check settings. If using the Cloud, check account."));
1076                             SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, DisconnectCause.InvalidAuthentication);
1077                         }
1078                         
else if (operationResponse.ReturnCode == ErrorCode.CustomAuthenticationFailed)
1079                         {
1080                             Debug.LogError(
string.Format("Custom Authentication failed (either due to user-input or configuration or AuthParameter string format). Calling: OnCustomAuthenticationFailed()"));
1081                             SendMonoMessage(PhotonNetworkingMessage.OnCustomAuthenticationFailed, operationResponse.DebugMessage);
1082                         }
1083                         
else
1084                         {
1085                             Debug.LogError(
string.Format("Authentication failed: '{0}' Code: {1}", operationResponse.DebugMessage, operationResponse.ReturnCode));
1086                         }
1087
1088                         
this.State = global::PeerState.Disconnecting;
1089                         
this.Disconnect();
1090
1091                         
if (operationResponse.ReturnCode == ErrorCode.MaxCcuReached)
1092                         {
1093                             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1094                                 Debug.LogWarning(
string.Format("Currently, the limit of users is reached for this title. Try again later. Disconnecting"));
1095                             SendMonoMessage(PhotonNetworkingMessage.OnPhotonMaxCccuReached);
1096                             SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.MaxCcuReached);
1097                         }
1098                         
else if (operationResponse.ReturnCode == ErrorCode.InvalidRegion)
1099                         {
1100                             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1101                                 Debug.LogError(
string.Format("The used master server address is not available with the subscription currently used. Got to Photon Cloud Dashboard or change URL. Disconnecting."));
1102                             SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.InvalidRegion);
1103                         }
1104                         
else if (operationResponse.ReturnCode == ErrorCode.AuthenticationTicketExpired)
1105                         {
1106                             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1107                                 Debug.LogError(
string.Format("The authentication ticket expired. You need to connect (and authenticate) again. Disconnecting."));
1108                             SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.AuthenticationTicketExpired);
1109                         }
1110                         
break;
1111                     }
1112                     
else
1113                     {
1114                         
if (this.server == ServerConnection.NameServer)
1115                         {
1116                             
// on the NameServer, authenticate returns the MasterServer address for a region and we hop off to there
1117                             
this.MasterServerAddress = operationResponse[ParameterCode.Address] as string;
1118                             
this.DisconnectToReconnect();
1119                         }
1120                         
else if (this.server == ServerConnection.MasterServer)
1121                         {
1122                             
if (PhotonNetwork.autoJoinLobby)
1123                             {
1124                                 
this.State = global::PeerState.Authenticated;
1125                                 
this.OpJoinLobby(this.lobby);
1126                             }
1127                             
else
1128                             {
1129                                 
this.State = global::PeerState.ConnectedToMaster;
1130                                 NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnConnectedToMaster);
1131                             }
1132                         }
1133                         
else if (this.server == ServerConnection.GameServer)
1134                         {
1135                             
this.State = global::PeerState.Joining;
1136
1137                             
if (this.mLastJoinType == JoinType.JoinGame || this.mLastJoinType == JoinType.JoinRandomGame || this.mLastJoinType == JoinType.JoinOrCreateOnDemand)
1138                             {
1139                                 
// if we just "join" the game, do so. if we wanted to "create the room on demand", we have to send this to the game server as well.
1140                                 
this.OpJoinRoom(this.mRoomToGetInto.name, this.mRoomOptionsForCreate, this.mRoomToEnterLobby, this.mLastJoinType == JoinType.JoinOrCreateOnDemand);
1141                             }
1142                             
else if (this.mLastJoinType == JoinType.CreateGame)
1143                             {
1144                                 
// on the game server, we have to apply the room properties that were chosen for creation of the room, so we use this.mRoomToGetInto
1145                                 
this.OpCreateGame(this.mRoomToGetInto.name, this.mRoomOptionsForCreate, this.mRoomToEnterLobby);
1146                             }
1147
1148                             
break;
1149                         }
1150                     }
1151                     
break;
1152                 }
1153
1154             
case OperationCode.GetRegions:
1155                 
// Debug.Log("GetRegions returned: " + operationResponse.ToStringFull());
1156
1157                 
if (operationResponse.ReturnCode == ErrorCode.InvalidAuthentication)
1158                 {
1159                     Debug.LogError(
string.Format("The appId this client sent is unknown on the server (Cloud). Check settings. If using the Cloud, check account."));
1160                     SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, DisconnectCause.InvalidAuthentication);
1161
1162                     
this.State = global::PeerState.Disconnecting;
1163                     
this.Disconnect();
1164                     
return;
1165                 }
1166
1167                 
string[] regions = operationResponse[ParameterCode.Region] as string[];
1168                 
string[] servers = operationResponse[ParameterCode.Address] as string[];
1169
1170                 
if (regions == null || servers == null || regions.Length != servers.Length)
1171                 {
1172                     Debug.LogError(
"The region arrays from Name Server are not ok. Must be non-null and same length.");
1173                     
break;
1174                 }
1175
1176                 
this.AvailableRegions = new List<Region>(regions.Length);
1177                 
for (int i = 0; i < regions.Length; i++)
1178                 {
1179                     
string regionCodeString = regions[i];
1180                     
if (string.IsNullOrEmpty(regionCodeString))
1181                     {
1182                         
continue;
1183                     }
1184                     regionCodeString = regionCodeString.ToLower();
1185
1186                     CloudRegionCode code = Region.Parse(regionCodeString);
1187                     
this.AvailableRegions.Add(new Region() { Code = code, HostAndPort = servers[i] });
1188                 }
1189
1190                 
// PUN assumes you fetch the name-server's list of regions to ping them
1191                 
if (PhotonNetwork.PhotonServerSettings.HostType == ServerSettings.HostingOption.BestRegion)
1192                 {
1193                     PhotonHandler.PingAvailableRegionsAndConnectToBest();
1194                 }
1195                 
break;
1196
1197             
case OperationCode.CreateGame:
1198                 {
1199                     
if (this.server == ServerConnection.GameServer)
1200                     {
1201                         
this.GameEnteredOnGameServer(operationResponse);
1202                     }
1203                     
else
1204                     {
1205                         
if (operationResponse.ReturnCode != 0)
1206                         {
1207                             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1208                                 Debug.LogWarning(
string.Format("CreateRoom failed, client stays on masterserver: {0}.", operationResponse.ToStringFull()));
1209
1210                             SendMonoMessage(PhotonNetworkingMessage.OnPhotonCreateRoomFailed);
1211                             
break;
1212                         }
1213
1214                         
string gameID = (string) operationResponse[ParameterCode.RoomName];
1215                         
if (!string.IsNullOrEmpty(gameID))
1216                         {
1217                             
// is only sent by the server's response, if it has not been
1218                             
// sent with the client's request before!
1219                             
this.mRoomToGetInto.name = gameID;
1220                         }
1221
1222                         
this.mGameserver = (string)operationResponse[ParameterCode.Address];
1223                         
this.DisconnectToReconnect();
1224                     }
1225
1226                     
break;
1227                 }
1228
1229             
case OperationCode.JoinGame:
1230                 {
1231                     
if (this.server != ServerConnection.GameServer)
1232                     {
1233                         
if (operationResponse.ReturnCode != 0)
1234                         {
1235                             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1236                                 Debug.Log(
string.Format("JoinRoom failed (room maybe closed by now). Client stays on masterserver: {0}. State: {1}", operationResponse.ToStringFull(), this.State));
1237
1238                             SendMonoMessage(PhotonNetworkingMessage.OnPhotonJoinRoomFailed);
1239                             
break;
1240                         }
1241
1242                         
this.mGameserver = (string)operationResponse[ParameterCode.Address];
1243                         
this.DisconnectToReconnect();
1244                     }
1245                     
else
1246                     {
1247                         
this.GameEnteredOnGameServer(operationResponse);
1248                     }
1249
1250                     
break;
1251                 }
1252
1253             
case OperationCode.JoinRandomGame:
1254                 {
1255                     
// happens only on master. on gameserver, this is a regular join (we don't need to find a random game again)
1256                     
// the operation OpJoinRandom either fails (with returncode 8) or returns game-to-join information
1257                     
if (operationResponse.ReturnCode != 0)
1258                     {
1259                         
if (operationResponse.ReturnCode == ErrorCode.NoRandomMatchFound)
1260                         {
1261                             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
1262                                 Debug.Log(
"JoinRandom failed: No open game. Calling: OnPhotonRandomJoinFailed() and staying on master server.");
1263                         }
1264                         
else if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1265                         {
1266                             Debug.LogWarning(
string.Format("JoinRandom failed: {0}.", operationResponse.ToStringFull()));
1267                         }
1268
1269                         SendMonoMessage(PhotonNetworkingMessage.OnPhotonRandomJoinFailed, operationResponse.ReturnCode, operationResponse.DebugMessage);
1270                         
break;
1271                     }
1272
1273                     
string roomName = (string)operationResponse[ParameterCode.RoomName];
1274                     
this.mRoomToGetInto.name = roomName;
1275                     
this.mGameserver = (string)operationResponse[ParameterCode.Address];
1276                     
this.DisconnectToReconnect();
1277                     
break;
1278                 }
1279
1280             
case OperationCode.JoinLobby:
1281                 
this.State = global::PeerState.JoinedLobby;
1282                 
this.insideLobby = true;
1283                 SendMonoMessage(PhotonNetworkingMessage.OnJoinedLobby);
1284
1285                 
// this.mListener.joinLobbyReturn();
1286                 
break;
1287             
case OperationCode.LeaveLobby:
1288                 
this.State = global::PeerState.Authenticated;
1289                 
this.LeftLobbyCleanup(); // will set insideLobby = false
1290                 
break;
1291
1292             
case OperationCode.Leave:
1293                 
this.DisconnectToReconnect();
1294                 
break;
1295
1296             
case OperationCode.SetProperties:
1297                 
// this.mListener.setPropertiesReturn(returnCode, debugMsg);
1298                 
break;
1299
1300             
case OperationCode.GetProperties:
1301                 {
1302                     Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
1303                     Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
1304                     
this.ReadoutProperties(gameProperties, actorProperties, 0);
1305
1306                     
// RemoveByteTypedPropertyKeys(actorProperties, false);
1307                     
// RemoveByteTypedPropertyKeys(gameProperties, false);
1308                     
// this.mListener.getPropertiesReturn(gameProperties, actorProperties, returnCode, debugMsg);
1309                     
break;
1310                 }
1311
1312             
case OperationCode.RaiseEvent:
1313                 
// this usually doesn't give us a result. only if the caching is affected the server will send one.
1314                 
break;
1315
1316             
case OperationCode.FindFriends:
1317                 
bool[] onlineList = operationResponse[ParameterCode.FindFriendsResponseOnlineList] as bool[];
1318                 
string[] roomList = operationResponse[ParameterCode.FindFriendsResponseRoomIdList] as string[];
1319
1320                 
if (onlineList != null && roomList != null && this.friendListRequested != null && onlineList.Length == this.friendListRequested.Length)
1321                 {
1322                     List<FriendInfo> friendList =
new List<FriendInfo>(this.friendListRequested.Length);
1323                     
for (int index = 0; index < this.friendListRequested.Length; index++)
1324                     {
1325                         FriendInfo friend =
new FriendInfo();
1326                         friend.Name =
this.friendListRequested[index];
1327                         friend.Room = roomList[index];
1328                         friend.IsOnline = onlineList[index];
1329                         friendList.Insert(index, friend);
1330                     }
1331                     PhotonNetwork.Friends = friendList;
1332                 }
1333                 
else
1334                 {
1335                     
// any of the lists is null and shouldn't. print a error
1336                     Debug.LogError(
"FindFriends failed to apply the result, as a required value wasn't provided or the friend list length differed from result.");
1337                 }
1338
1339                 
this.friendListRequested = null;
1340                 
this.isFetchingFriends = false;
1341                 
this.friendListTimestamp = Environment.TickCount;
1342                 
if (this.friendListTimestamp == 0)
1343                 {
1344                     
this.friendListTimestamp = 1; // makes sure the timestamp is not accidentally 0
1345                 }
1346
1347                 SendMonoMessage(PhotonNetworkingMessage.OnUpdatedFriendList);
1348                 
break;
1349
1350             
case OperationCode.WebRpc:
1351                 SendMonoMessage(PhotonNetworkingMessage.OnWebRpcResponse, operationResponse);
1352                 
break;
1353
1354             
default:
1355                 Debug.LogWarning(
string.Format("OperationResponse unhandled: {0}", operationResponse.ToString()));
1356                 
break;
1357         }
1358
1359         
this.externalListener.OnOperationResponse(operationResponse);
1360     }

1361
1362
1363     ///
<summary>Contains the list of names of friends to look up their state on the server.</summary>
1364     
private string[] friendListRequested;
1365
1366     ///
<summary>
1367     ///
Age of friend list info (in milliseconds). It's 0 until a friend list is fetched.
1368     ///
</summary>
1369     
protected internal int FriendsListAge { get { return (this.isFetchingFriends || this.friendListTimestamp == 0) ? 0 : Environment.TickCount - this.friendListTimestamp; } }
1370
1371     
private int friendListTimestamp;
1372
1373     ///
<summary>Internal flag to know if the client currently fetches a friend list.</summary>
1374     
private bool isFetchingFriends;
1375
1376     ///
<summary>
1377     ///
Request the rooms and online status for a list of friends. All client must set a unique username via PlayerName property. The result is available in this.Friends.
1378     ///
</summary>
1379     ///
<remarks>
1380     ///
Used on Master Server to find the rooms played by a selected list of users.
1381     ///
The result will be mapped to LoadBalancingClient.Friends when available.
1382     ///
The list is initialized by OpFindFriends on first use (before that, it is null).
1383     ///
1384     ///
Users identify themselves by setting a PlayerName in the LoadBalancingClient instance.
1385     ///
This in turn will send the name in OpAuthenticate after each connect (to master and game servers).
1386     ///
Note: Changing a player's name doesn't make sense when using a friend list.
1387     ///
1388     ///
The list of usernames must be fetched from some other source (not provided by Photon).
1389     ///
1390     ///
1391     ///
Internal:
1392     ///
The server response includes 2 arrays of info (each index matching a friend from the request):
1393     ///
ParameterCode.FindFriendsResponseOnlineList = bool[] of online states
1394     ///
ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)
1395     ///
</remarks>
1396     ///
<param name="friendsToFind">Array of friend's names (make sure they are unique).</param>
1397     ///
<returns>If the operation could be sent (requires connection, only one request is allowed at any time). Always false in offline mode.</returns>
1398     
public override bool OpFindFriends(string[] friendsToFind)
1399     {
1400         
if (this.isFetchingFriends)
1401         {
1402             
return false; // fetching friends currently, so don't do it again (avoid changing the list while fetching friends)
1403         }
1404
1405         
this.friendListRequested = friendsToFind;
1406         
this.isFetchingFriends = true;
1407
1408         
return base.OpFindFriends(friendsToFind);
1409     }
1410
1411     
public void OnStatusChanged(StatusCode statusCode)
1412     {
1413         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1414             Debug.Log(
string.Format("OnStatusChanged: {0}", statusCode.ToString()));
1415
1416         
switch (statusCode)
1417         {
1418             
case StatusCode.Connect:
1419                 
if (this.State == global::PeerState.ConnectingToNameServer)
1420                 {
1421                     
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
1422                         Debug.Log(
"Connected to NameServer.");
1423
1424                     
this.server = ServerConnection.NameServer;
1425                     
if (this.CustomAuthenticationValues != null)
1426                     {
1427                         
this.CustomAuthenticationValues.Secret = null; // when connecting to NameServer, invalidate any auth values
1428                     }
1429                 }
1430
1431                 
if (this.State == global::PeerState.ConnectingToGameserver)
1432                 {
1433                     
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
1434                         Debug.Log(
"Connected to gameserver.");
1435
1436                     
this.server = ServerConnection.GameServer;
1437                     
this.State = global::PeerState.ConnectedToGameserver;
1438                 }
1439
1440                 
if (this.State == global::PeerState.ConnectingToMasterserver)
1441                 {
1442                     
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
1443                         Debug.Log(
"Connected to masterserver.");
1444
1445                     
this.server = ServerConnection.MasterServer;
1446                     
this.State = global::PeerState.ConnectedToMaster;
1447
1448                     
if (this.IsInitialConnect)
1449                     {
1450                         
this.IsInitialConnect = false; // after handling potential initial-connect issues with special messages, we are now sure we can reach a server
1451                         SendMonoMessage(PhotonNetworkingMessage.OnConnectedToPhoton);
1452                     }
1453                 }
1454
1455                 
this.EstablishEncryption(); // always enable encryption
1456
1457                 
if (this.IsAuthorizeSecretAvailable)
1458                 {
1459                     
// if we have a token we don't have to wait for encryption (it is encrypted anyways, so encryption is just optional later on)
1460                     
this.didAuthenticate = this.OpAuthenticate(this.mAppId, this.mAppVersionPun, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion.ToString());
1461                     
if (this.didAuthenticate)
1462                     {
1463                         
this.State = global::PeerState.Authenticating;
1464                     }
1465                 }
1466                 
break;
1467
1468             
case StatusCode.EncryptionEstablished:
1469                 
// on nameserver, the "process" is stopped here, so the developer/game can either get regions or authenticate with a specific region
1470                 
if (this.server == ServerConnection.NameServer)
1471                 {
1472                     
this.State = global::PeerState.ConnectedToNameServer;
1473
1474                     
if (!this.didAuthenticate && this.CloudRegion == CloudRegionCode.none)
1475                     {
1476                         
// this client is not setup to connect to a default region. find out which regions there are!
1477                         
this.OpGetRegions(this.mAppId);
1478                     }
1479                 }
1480
1481                 
// we might need to authenticate automatically now, so the client can do anything at all
1482                 
if (!this.didAuthenticate && (!this.IsUsingNameServer || this.CloudRegion != CloudRegionCode.none))
1483                 {
1484                     
// once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
1485                     
this.didAuthenticate = this.OpAuthenticate(this.mAppId, this.mAppVersionPun, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion.ToString());
1486                     
if (this.didAuthenticate)
1487                     {
1488                         
this.State = global::PeerState.Authenticating;
1489                     }
1490                 }
1491                 
break;
1492
1493             
case StatusCode.EncryptionFailedToEstablish:
1494                 Debug.LogError(
"Encryption wasn't established: " + statusCode + ". Going to authenticate anyways.");
1495                 
this.OpAuthenticate(this.mAppId, this.mAppVersionPun, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion.ToString()); // TODO: check if there are alternatives
1496                 
break;
1497
1498             
case StatusCode.Disconnect:
1499                 
this.didAuthenticate = false;
1500                 
this.isFetchingFriends = false;
1501                 
if (server == ServerConnection.GameServer) this.LeftRoomCleanup();
1502                 
if (server == ServerConnection.MasterServer) this.LeftLobbyCleanup();
1503
1504                 
if (this.State == global::PeerState.DisconnectingFromMasterserver)
1505                 {
1506                     
if (this.Connect(this.mGameserver, ServerConnection.GameServer))
1507                     {
1508                         
this.State = global::PeerState.ConnectingToGameserver;
1509                     }
1510                 }
1511                 
else if (this.State == global::PeerState.DisconnectingFromGameserver || this.State == global::PeerState.DisconnectingFromNameServer)
1512                 {
1513                     
if (this.Connect(this.MasterServerAddress, ServerConnection.MasterServer))
1514                     {
1515                         
this.State = global::PeerState.ConnectingToMasterserver;
1516                     }
1517                 }
1518                 
else
1519                 {
1520                     
if (this.CustomAuthenticationValues != null)
1521                     {
1522                         
this.CustomAuthenticationValues.Secret = null; // invalidate any custom auth secrets
1523                     }
1524
1525                     
this.State = global::PeerState.PeerCreated; // if we set another state here, we could keep clients from connecting in OnDisconnectedFromPhoton right here.
1526                     SendMonoMessage(PhotonNetworkingMessage.OnDisconnectedFromPhoton);
1527                 }
1528                 
break;
1529
1530             
case StatusCode.SecurityExceptionOnConnect:
1531             
case StatusCode.ExceptionOnConnect:
1532                 
this.State = global::PeerState.PeerCreated;
1533                 
if (this.CustomAuthenticationValues != null)
1534                 {
1535                     
this.CustomAuthenticationValues.Secret = null; // invalidate any custom auth secrets
1536                 }
1537
1538                 DisconnectCause cause = (DisconnectCause)statusCode;
1539                 SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
1540                 
break;
1541
1542             
case StatusCode.Exception:
1543                 
if (this.IsInitialConnect)
1544                 {
1545                     Debug.LogError(
"Exception while connecting to: " + this.ServerAddress + ". Check if the server is available.");
1546                     
if (this.ServerAddress == null || this.ServerAddress.StartsWith("127.0.0.1"))
1547                     {
1548                         Debug.LogWarning(
"The server address is 127.0.0.1 (localhost): Make sure the server is running on this machine. Android and iOS emulators have their own localhost.");
1549                         
if (this.ServerAddress == this.mGameserver)
1550                         {
1551                             Debug.LogWarning(
"This might be a misconfiguration in the game server config. You need to edit it to a (public) address.");
1552                         }
1553                     }
1554
1555                     
this.State = global::PeerState.PeerCreated;
1556                     cause = (DisconnectCause)statusCode;
1557                     SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
1558                 }
1559                 
else
1560                 {
1561                     
this.State = global::PeerState.PeerCreated;
1562
1563                     cause = (DisconnectCause)statusCode;
1564                     SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, cause);
1565                 }
1566
1567                 
this.Disconnect();
1568                 
break;
1569
1570             
case StatusCode.TimeoutDisconnect:
1571             
case StatusCode.ExceptionOnReceive:
1572             
case StatusCode.DisconnectByServer:
1573             
case StatusCode.DisconnectByServerLogic:
1574             
case StatusCode.DisconnectByServerUserLimit:
1575                 
if (this.IsInitialConnect)
1576                 {
1577                     Debug.LogWarning(statusCode +
" while connecting to: " + this.ServerAddress + ". Check if the server is available.");
1578
1579                     cause = (DisconnectCause)statusCode;
1580                     SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
1581                 }
1582                 
else
1583                 {
1584                     cause = (DisconnectCause)statusCode;
1585                     SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, cause);
1586                 }
1587                 
if (this.CustomAuthenticationValues != null)
1588                 {
1589                     
this.CustomAuthenticationValues.Secret = null; // invalidate any custom auth secrets
1590                 }
1591
1592                 
this.Disconnect();
1593                 
break;
1594
1595             
case StatusCode.SendError:
1596                 
// this.mListener.clientErrorReturn(statusCode);
1597                 
break;
1598
1599             
case StatusCode.QueueOutgoingReliableWarning:
1600             
case StatusCode.QueueOutgoingUnreliableWarning:
1601             
case StatusCode.QueueOutgoingAcksWarning:
1602             
case StatusCode.QueueSentWarning:
1603                 
// this.mListener.warningReturn(statusCode);
1604                 
break;
1605
1606             
case StatusCode.QueueIncomingReliableWarning:
1607             
case StatusCode.QueueIncomingUnreliableWarning:
1608                 Debug.Log(statusCode +
". This client buffers many incoming messages. This is OK temporarily. With lots of these warnings, check if you send too much or execute messages too slow. " + (PhotonNetwork.isMessageQueueRunning? "":"Your isMessageQueueRunning is false. This can cause the issue temporarily.") );
1609                 
break;
1610
1611             
// // TCP "routing" is an option of Photon that's not currently needed (or supported) by PUN
1612             
//case StatusCode.TcpRouterResponseOk:
1613             
// break;
1614             
//case StatusCode.TcpRouterResponseEndpointUnknown:
1615             
//case StatusCode.TcpRouterResponseNodeIdUnknown:
1616             
//case StatusCode.TcpRouterResponseNodeNotReady:
1617
1618             
// this.DebugReturn(DebugLevel.ERROR, "Unexpected router response: " + statusCode);
1619             
// break;
1620
1621             
default:
1622
1623                 
// this.mListener.serverErrorReturn(statusCode.value());
1624                 Debug.LogError(
"Received unknown status code: " + statusCode);
1625                 
break;
1626         }
1627
1628         
this.externalListener.OnStatusChanged(statusCode);
1629     }
1630
1631     
public void OnEvent(EventData photonEvent)
1632     {
1633         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
1634             Debug.Log(
string.Format("OnEvent: {0}", photonEvent.ToString()));
1635
1636         
int actorNr = -1;
1637         PhotonPlayer originatingPlayer =
null;
1638
1639         
if (photonEvent.Parameters.ContainsKey(ParameterCode.ActorNr))
1640         {
1641             actorNr = (
int)photonEvent[ParameterCode.ActorNr];
1642             
if (this.mActors.ContainsKey(actorNr))
1643             {
1644                 originatingPlayer = (PhotonPlayer)
this.mActors[actorNr];
1645             }
1646             
//else
1647             
//{
1648             
// // the actor sending this event is not in actorlist. this is usually no problem
1649             
// if (photonEvent.Code != (byte)LiteOpCode.Join)
1650             
// {
1651             
// Debug.LogWarning("Received event, but we do not have this actor: " + actorNr);
1652             
// }
1653             
//}
1654         }
1655
1656         
switch (photonEvent.Code)
1657         {
1658             
case PunEvent.OwnershipRequest:
1659             {
1660                 
int[] requestValues = (int[]) photonEvent.Parameters[ParameterCode.CustomEventContent];
1661                 
int requestedViewId = requestValues[0];
1662                 
int currentOwner = requestValues[1];
1663                 Debug.Log(
"Ev OwnershipRequest: " + photonEvent.Parameters.ToStringFull() + " ViewID: " + requestedViewId + " from: " + currentOwner + " Time: " + Environment.TickCount%1000);
1664
1665                 PhotonView requestedView = PhotonView.Find(requestedViewId);
1666                 
if (requestedView == null)
1667                 {
1668                     Debug.LogWarning(
"Can't find PhotonView of incoming OwnershipRequest. ViewId not found: " + requestedViewId);
1669                     
break;
1670                 }
1671
1672                 Debug.Log(
"Ev OwnershipRequest PhotonView.ownershipTransfer: " + requestedView.ownershipTransfer + " .ownerId: " + requestedView.ownerId + " isOwnerActive: " + requestedView.isOwnerActive + ". This client's player: " + PhotonNetwork.player.ToStringFull());
1673
1674                 
switch (requestedView.ownershipTransfer)
1675                 {
1676                     
case OwnershipOption.Fixed:
1677                         Debug.LogWarning(
"Ownership mode == fixed. Ignoring request.");
1678                         
break;
1679                     
case OwnershipOption.Takeover:
1680                         
if (currentOwner == requestedView.ownerId)
1681                         {
1682                             
// a takeover is successful automatically, if taken from current owner
1683                             requestedView.ownerId = actorNr;
1684                         }
1685                         
break;
1686                     
case OwnershipOption.Request:
1687                         
if (currentOwner == PhotonNetwork.player.ID || PhotonNetwork.player.isMasterClient)
1688                         {
1689                             
if ((requestedView.ownerId == PhotonNetwork.player.ID) || (PhotonNetwork.player.isMasterClient && !requestedView.isOwnerActive))
1690                             {
1691                                 SendMonoMessage(PhotonNetworkingMessage.OnOwnershipRequest,
new object[] {requestedView, originatingPlayer});
1692                             }
1693                         }
1694                         
break;
1695                     
default:
1696                         
break;
1697                 }
1698             }
1699                 
break;
1700
1701             
case PunEvent.OwnershipTransfer:
1702                 {
1703                     
int[] transferViewToUserID = (int[]) photonEvent.Parameters[ParameterCode.CustomEventContent];
1704                     Debug.Log(
"Ev OwnershipTransfer. ViewID " + transferViewToUserID[0] + " to: " + transferViewToUserID[1] + " Time: " + Environment.TickCount%1000);
1705
1706                     
int requestedViewId = transferViewToUserID[0];
1707                     
int newOwnerId = transferViewToUserID[1];
1708
1709                     PhotonView pv = PhotonView.Find(requestedViewId);
1710                     pv.ownerId = newOwnerId;
1711
1712                     
break;
1713                 }
1714             
case EventCode.GameList:
1715                 {
1716                     
this.mGameList = new Dictionary<string, RoomInfo>();
1717                     Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
1718                     
foreach (DictionaryEntry game in games)
1719                     {
1720                         
string gameName = (string)game.Key;
1721                         
this.mGameList[gameName] = new RoomInfo(gameName, (Hashtable)game.Value);
1722                     }
1723                     mGameListCopy =
new RoomInfo[mGameList.Count];
1724                     mGameList.Values.CopyTo(mGameListCopy,
0);
1725                     SendMonoMessage(PhotonNetworkingMessage.OnReceivedRoomListUpdate);
1726                     
break;
1727                 }
1728
1729             
case EventCode.GameListUpdate:
1730                 {
1731                     Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
1732                     
foreach (DictionaryEntry room in games)
1733                     {
1734                         
string gameName = (string)room.Key;
1735                         RoomInfo game =
new RoomInfo(gameName, (Hashtable)room.Value);
1736                         
if (game.removedFromList)
1737                         {
1738                             
this.mGameList.Remove(gameName);
1739                         }
1740                         
else
1741                         {
1742                             
this.mGameList[gameName] = game;
1743                         }
1744                     }
1745                     
this.mGameListCopy = new RoomInfo[this.mGameList.Count];
1746                     
this.mGameList.Values.CopyTo(this.mGameListCopy, 0);
1747                     SendMonoMessage(PhotonNetworkingMessage.OnReceivedRoomListUpdate);
1748                     
break;
1749                 }
1750
1751             
case EventCode.QueueState:
1752                 
// not used anymore
1753                 
break;
1754
1755             
case EventCode.AppStats:
1756                 
// Debug.LogInfo("Received stats!");
1757                 
this.mPlayersInRoomsCount = (int)photonEvent[ParameterCode.PeerCount];
1758                 
this.mPlayersOnMasterCount = (int)photonEvent[ParameterCode.MasterPeerCount];
1759                 
this.mGameCount = (int)photonEvent[ParameterCode.GameCount];
1760                 
break;
1761
1762             
case EventCode.Join:
1763                 
// actorNr is fetched out of event above
1764                 Hashtable actorProperties = (Hashtable)photonEvent[ParameterCode.PlayerProperties];
1765                 
if (originatingPlayer == null)
1766                 {
1767                     
bool isLocal = this.mLocalActor.ID == actorNr;
1768                     
this.AddNewPlayer(actorNr, new PhotonPlayer(isLocal, actorNr, actorProperties));
1769                     
this.ResetPhotonViewsOnSerialize(); // This sets the correct OnSerializeState for Reliable OnSerialize
1770                 }
1771
1772                 
if (actorNr == this.mLocalActor.ID)
1773                 {
1774                     
// in this player's 'own' join event, we get a complete list of players in the room, so check if we know all players
1775                     
int[] actorsInRoom = (int[])photonEvent[ParameterCode.ActorList];
1776                     
foreach (int actorNrToCheck in actorsInRoom)
1777                     {
1778                         
if (this.mLocalActor.ID != actorNrToCheck && !this.mActors.ContainsKey(actorNrToCheck))
1779                         {
1780                             
this.AddNewPlayer(actorNrToCheck, new PhotonPlayer(false, actorNrToCheck, string.Empty));
1781                         }
1782                     }
1783
1784                     
// joinWithCreateOnDemand can turn an OpJoin into creating the room. Then actorNumber is 1 and callback: OnCreatedRoom()
1785                     
if (this.mLastJoinType == JoinType.JoinOrCreateOnDemand && this.mLocalActor.ID == 1)
1786                     {
1787                         SendMonoMessage(PhotonNetworkingMessage.OnCreatedRoom);
1788                     }
1789                     SendMonoMessage(PhotonNetworkingMessage.OnJoinedRoom);
//Always send OnJoinedRoom
1790
1791                 }
1792                 
else
1793                 {
1794                     SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerConnected,
this.mActors[actorNr]);
1795                 }
1796                 
break;
1797
1798             
case EventCode.Leave:
1799                 
this.HandleEventLeave(actorNr);
1800                 
break;
1801
1802             
case EventCode.PropertiesChanged:
1803                 
int targetActorNr = (int)photonEvent[ParameterCode.TargetActorNr];
1804                 Hashtable gameProperties =
null;
1805                 Hashtable actorProps =
null;
1806                 
if (targetActorNr == 0)
1807                 {
1808                     gameProperties = (Hashtable)photonEvent[ParameterCode.Properties];
1809                 }
1810                 
else
1811                 {
1812                     actorProps = (Hashtable)photonEvent[ParameterCode.Properties];
1813                 }
1814
1815                 
this.ReadoutProperties(gameProperties, actorProps, targetActorNr);
1816                 
break;
1817
1818             
case PunEvent.RPC:
1819                 
//ts: each event now contains a single RPC. execute this
1820                 
// Debug.Log("Ev RPC from: " + originatingPlayer);
1821                 
this.ExecuteRPC(photonEvent[ParameterCode.Data] as Hashtable, originatingPlayer);
1822                 
break;
1823
1824             
case PunEvent.SendSerialize:
1825             
case PunEvent.SendSerializeReliable:
1826                 Hashtable serializeData = (Hashtable)photonEvent[ParameterCode.Data];
1827                 
//Debug.Log(serializeData.ToStringFull());
1828
1829                 
int remoteUpdateServerTimestamp = (int)serializeData[(byte)0];
1830                 
short remoteLevelPrefix = -1;
1831                 
short initialDataIndex = 1;
1832                 
if (serializeData.ContainsKey((byte)1))
1833                 {
1834                     remoteLevelPrefix = (
short)serializeData[(byte)1];
1835                     initialDataIndex =
2;
1836                 }
1837
1838                 
for (short s = initialDataIndex; s < serializeData.Count; s++)
1839                 {
1840                     
this.OnSerializeRead(serializeData[s] as Hashtable, originatingPlayer, remoteUpdateServerTimestamp, remoteLevelPrefix);
1841                 }
1842                 
break;
1843
1844             
case PunEvent.Instantiation:
1845                 
this.DoInstantiate((Hashtable)photonEvent[ParameterCode.Data], originatingPlayer, null);
1846                 
break;
1847
1848             
case PunEvent.CloseConnection:
1849                 
// MasterClient "requests" a disconnection from us
1850                 
if (originatingPlayer == null || !originatingPlayer.isMasterClient)
1851                 {
1852                     Debug.LogError(
"Error: Someone else(" + originatingPlayer + ") then the masterserver requests a disconnect!");
1853                 }
1854                 
else
1855                 {
1856                     PhotonNetwork.LeaveRoom();
1857                 }
1858
1859                 
break;
1860
1861             
case PunEvent.DestroyPlayer:
1862                 Hashtable evData = (Hashtable)photonEvent[ParameterCode.Data];
1863                 
int targetPlayerId = (int)evData[(byte)0];
1864                 
if (targetPlayerId >= 0)
1865                 {
1866                     
this.DestroyPlayerObjects(targetPlayerId, true);
1867                 }
1868                 
else
1869                 {
1870                     
if (this.DebugOut >= DebugLevel.INFO) Debug.Log("Ev DestroyAll! By PlayerId: " + actorNr);
1871                     
this.DestroyAll(true);
1872                 }
1873                 
break;
1874
1875             
case PunEvent.Destroy:
1876                 evData = (Hashtable)photonEvent[ParameterCode.Data];
1877                 
int instantiationId = (int)evData[(byte)0];
1878                 
// Debug.Log("Ev Destroy for viewId: " + instantiationId + " sent by owner: " + (instantiationId / PhotonNetwork.MAX_VIEW_IDS == actorNr) + " this client is owner: " + (instantiationId / PhotonNetwork.MAX_VIEW_IDS == this.mLocalActor.ID));
1879
1880
1881                 PhotonView pvToDestroy =
null;
1882                 
if (this.photonViewList.TryGetValue(instantiationId, out pvToDestroy))
1883                 {
1884                     
this.RemoveInstantiatedGO(pvToDestroy.gameObject, true);
1885                 }
1886                 
else
1887                 {
1888                     
if (this.DebugOut >= DebugLevel.ERROR) Debug.LogError("Ev Destroy Failed. Could not find PhotonView with instantiationId " + instantiationId + ". Sent by actorNr: " + actorNr);
1889                 }
1890
1891                 
break;
1892
1893             
case PunEvent.AssignMaster:
1894                 evData = (Hashtable)photonEvent[ParameterCode.Data];
1895                 
int newMaster = (int)evData[(byte)1];
1896                 
this.SetMasterClient(newMaster, false);
1897                 
break;
1898
1899             
default:
1900                 
if (photonEvent.Code < 200 && PhotonNetwork.OnEventCall != null)
1901                 {
1902                     
object content = photonEvent[ParameterCode.Data];
1903                     PhotonNetwork.OnEventCall(photonEvent.Code, content, actorNr);
1904                 }
1905                 
else
1906                 {
1907                     
// actorNr might be null. it is fetched out of event on top of method
1908                     
// Hashtable eventContent = (Hashtable) photonEvent[ParameterCode.Data];
1909                     
// this.mListener.customEventAction(actorNr, eventCode, eventContent);
1910                     Debug.LogError(
"Error. Unhandled event: " + photonEvent);
1911                 }
1912                 
break;
1913         }
1914
1915         
this.externalListener.OnEvent(photonEvent);
1916     }
1917
1918     
private void SendVacantViewIds()
1919     {
1920         Debug.Log(
"SendVacantViewIds()");
1921         List<
int> vacantViews = new List<int>();
1922         
foreach (PhotonView view in this.photonViewList.Values)
1923         {
1924             
if (!view.isOwnerActive)
1925             {
1926                 vacantViews.Add(view.viewID);
1927             }
1928         }
1929
1930         Debug.Log(
"Sending vacant view IDs. Length: " + vacantViews.Count);
1931         
//this.OpRaiseEvent(PunEvent.VacantViewIds, true, vacantViews.ToArray());
1932         
this.OpRaiseEvent(PunEvent.VacantViewIds, vacantViews.ToArray(), true, null);
1933     }
1934
1935     
#endregion
1936
1937     
public static void SendMonoMessage(PhotonNetworkingMessage methodString, params object[] parameters)
1938     {
1939         HashSet<GameObject> objectsToCall;
1940         
if (PhotonNetwork.SendMonoMessageTargets != null)
1941         {
1942             objectsToCall = PhotonNetwork.SendMonoMessageTargets;
1943         }
1944         
else
1945         {
1946             objectsToCall = PhotonNetwork.FindGameObjectsWithComponent(PhotonNetwork.SendMonoMessageTargetType);
1947         }
1948
1949         
string methodName = methodString.ToString();
1950         
object callParameter = (parameters != null && parameters.Length == 1) ? parameters[0] : parameters;
1951         
foreach (GameObject gameObject in objectsToCall)
1952         {
1953             gameObject.SendMessage(methodName, callParameter, SendMessageOptions.DontRequireReceiver);
1954         }
1955     }
1956
1957     
// PHOTONVIEW/RPC related
1958
1959     ///
<summary>
1960     ///
Executes a received RPC event
1961     ///
</summary>
1962     
public void ExecuteRPC(Hashtable rpcData, PhotonPlayer sender)
1963     {
1964         
if (rpcData == null || !rpcData.ContainsKey((byte)0))
1965         {
1966             Debug.LogError(
"Malformed RPC; this should never occur. Content: " + SupportClass.DictionaryToString(rpcData));
1967             
return;
1968         }
1969
1970         
// ts: updated with "flat" event data
1971         
int netViewID = (int)rpcData[(byte)0]; // LIMITS PHOTONVIEWS&PLAYERS
1972         
int otherSidePrefix = 0; // by default, the prefix is 0 (and this is not being sent)
1973         
if (rpcData.ContainsKey((byte)1))
1974         {
1975             otherSidePrefix = (
short)rpcData[(byte)1];
1976         }
1977
1978         
string inMethodName;
1979         
if (rpcData.ContainsKey((byte)5))
1980         {
1981             
int rpcIndex = (byte)rpcData[(byte)5]; // LIMITS RPC COUNT
1982             
if (rpcIndex > PhotonNetwork.PhotonServerSettings.RpcList.Count - 1)
1983             {
1984                 Debug.LogError(
"Could not find RPC with index: " + rpcIndex + ". Going to ignore! Check PhotonServerSettings.RpcList");
1985                 
return;
1986             }
1987             
else
1988             {
1989                 inMethodName = PhotonNetwork.PhotonServerSettings.RpcList[rpcIndex];
1990             }
1991         }
1992         
else
1993         {
1994             inMethodName = (
string)rpcData[(byte)3];
1995         }
1996
1997         
object[] inMethodParameters = null;
1998         
if (rpcData.ContainsKey((byte)4))
1999         {
2000             inMethodParameters = (
object[])rpcData[(byte)4];
2001         }
2002
2003         
if (inMethodParameters == null)
2004         {
2005             inMethodParameters =
new object[0];
2006         }
2007
2008         PhotonView photonNetview =
this.GetPhotonView(netViewID);
2009         
if (photonNetview == null)
2010         {
2011             
int viewOwnerId = netViewID/PhotonNetwork.MAX_VIEW_IDS;
2012             
bool owningPv = (viewOwnerId == this.mLocalActor.ID);
2013             
bool ownerSent = (viewOwnerId == sender.ID);
2014
2015             
if (owningPv)
2016             {
2017                 Debug.LogWarning(
"Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist! View was/is ours." + (ownerSent ? " Owner called." : " Remote called.") + " By: " + sender.ID);
2018             }
2019             
else
2020             {
2021                 Debug.LogWarning(
"Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist! Was remote PV." + (ownerSent ? " Owner called." : " Remote called.") + " By: " + sender.ID + " Maybe GO was destroyed but RPC not cleaned up.");
2022             }
2023             
return;
2024         }
2025
2026         
if (photonNetview.prefix != otherSidePrefix)
2027         {
2028             Debug.LogError(
2029                 
"Received RPC \"" + inMethodName + "\" on viewID " + netViewID + " with a prefix of " + otherSidePrefix
2030                 +
", our prefix is " + photonNetview.prefix + ". The RPC has been ignored.");
2031             
return;
2032         }
2033
2034         
// Get method name
2035         
if (inMethodName == string.Empty)
2036         {
2037             Debug.LogError(
"Malformed RPC; this should never occur. Content: " + SupportClass.DictionaryToString(rpcData));
2038             
return;
2039         }
2040
2041         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
2042             Debug.Log(
"Received RPC: " + inMethodName);
2043
2044
2045         
// SetReceiving filtering
2046         
if (photonNetview.group != 0 && !allowedReceivingGroups.Contains(photonNetview.group))
2047         {
2048             
return; // Ignore group
2049         }
2050
2051         Type[] argTypes =
new Type[0];
2052         
if (inMethodParameters.Length > 0)
2053         {
2054             argTypes =
new Type[inMethodParameters.Length];
2055             
int i = 0;
2056             
for (int index = 0; index < inMethodParameters.Length; index++)
2057             {
2058                 
object objX = inMethodParameters[index];
2059                 
if (objX == null)
2060                 {
2061                     argTypes[i] =
null;
2062                 }
2063                 
else
2064                 {
2065                     argTypes[i] = objX.GetType();
2066                 }
2067
2068                 i++;
2069             }
2070         }
2071
2072         
int receivers = 0;
2073         
int foundMethods = 0;
2074         MonoBehaviour[] mbComponents = photonNetview.GetComponents<MonoBehaviour>();
// NOTE: we could possibly also cache MonoBehaviours per view?!
2075         
for (int componentsIndex = 0; componentsIndex < mbComponents.Length; componentsIndex++)
2076         {
2077             MonoBehaviour monob = mbComponents[componentsIndex];
2078             
if (monob == null)
2079             {
2080                 Debug.LogError(
"ERROR You have missing MonoBehaviours on your gameobjects!");
2081                 
continue;
2082             }
2083
2084             Type type = monob.GetType();
2085
2086             
// Get [RPC] methods from cache
2087             List<MethodInfo> cachedRPCMethods =
null;
2088             
if (this.monoRPCMethodsCache.ContainsKey(type))
2089             {
2090                 cachedRPCMethods =
this.monoRPCMethodsCache[type];
2091             }
2092
2093             
if (cachedRPCMethods == null)
2094             {
2095                 List<MethodInfo> entries = SupportClass.GetMethods(type,
typeof(RPC));
2096
2097                 
this.monoRPCMethodsCache[type] = entries;
2098                 cachedRPCMethods = entries;
2099             }
2100
2101             
if (cachedRPCMethods == null)
2102             {
2103                 
continue;
2104             }
2105
2106             
// Check cache for valid methodname+arguments
2107             
for (int index = 0; index < cachedRPCMethods.Count; index++)
2108             {
2109                 MethodInfo mInfo = cachedRPCMethods[index];
2110                 
if (mInfo.Name == inMethodName)
2111                 {
2112                     foundMethods++;
2113                     ParameterInfo[] pArray = mInfo.GetParameters();
2114                     
if (pArray.Length == argTypes.Length)
2115                     {
2116                         
// Normal, PhotonNetworkMessage left out
2117                         
if (this.CheckTypeMatch(pArray, argTypes))
2118                         {
2119                             receivers++;
2120                             
object result = mInfo.Invoke((object)monob, inMethodParameters);
2121                             
if (mInfo.ReturnType == typeof(IEnumerator))
2122                             {
2123                                 monob.StartCoroutine((IEnumerator)result);
2124                             }
2125                         }
2126                     }
2127                     
else if ((pArray.Length - 1) == argTypes.Length)
2128                     {
2129                         
// Check for PhotonNetworkMessage being the last
2130                         
if (this.CheckTypeMatch(pArray, argTypes))
2131                         {
2132                             
if (pArray[pArray.Length - 1].ParameterType == typeof(PhotonMessageInfo))
2133                             {
2134                                 receivers++;
2135
2136                                 
int sendTime = (int)rpcData[(byte)2];
2137                                 
object[] deParamsWithInfo = new object[inMethodParameters.Length + 1];
2138                                 inMethodParameters.CopyTo(deParamsWithInfo,
0);
2139                                 deParamsWithInfo[deParamsWithInfo.Length -
1] = new PhotonMessageInfo(sender, sendTime, photonNetview);
2140
2141                                 
object result = mInfo.Invoke((object)monob, deParamsWithInfo);
2142                                 
if (mInfo.ReturnType == typeof(IEnumerator))
2143                                 {
2144                                     monob.StartCoroutine((IEnumerator)result);
2145                                 }
2146                             }
2147                         }
2148                     }
2149                     
else if (pArray.Length == 1 && pArray[0].ParameterType.IsArray)
2150                     {
2151                         receivers++;
2152                         
object result = mInfo.Invoke((object)monob, new object[] { inMethodParameters });
2153                         
if (mInfo.ReturnType == typeof(IEnumerator))
2154                         {
2155                             monob.StartCoroutine((IEnumerator)result);
2156                         }
2157                     }
2158                 }
2159             }
2160         }
2161
2162         
// Error handling
2163         
if (receivers != 1)
2164         {
2165             
string argsString = string.Empty;
2166             
for (int index = 0; index < argTypes.Length; index++)
2167             {
2168                 Type ty = argTypes[index];
2169                 
if (argsString != string.Empty)
2170                 {
2171                     argsString +=
", ";
2172                 }
2173
2174                 
if (ty == null)
2175                 {
2176                     argsString +=
"null";
2177                 }
2178                 
else
2179                 {
2180                     argsString += ty.Name;
2181                 }
2182             }
2183
2184             
if (receivers == 0)
2185             {
2186                 
if (foundMethods == 0)
2187                 {
2188                     Debug.LogError(
"PhotonView with ID " + netViewID + " has no method \"" + inMethodName + "\" marked with the [RPC](C#) or @RPC(JS) property! Args: " + argsString);
2189                 }
2190                 
else
2191                 {
2192                     Debug.LogError(
"PhotonView with ID " + netViewID + " has no method \"" + inMethodName + "\" that takes " + argTypes.Length + " argument(s): " + argsString);
2193                 }
2194             }
2195             
else
2196             {
2197                 Debug.LogError(
"PhotonView with ID " + netViewID + " has " + receivers + " methods \"" + inMethodName + "\" that takes " + argTypes.Length + " argument(s): " + argsString + ". Should be just one?");
2198             }
2199         }
2200     }

2201
2202     ///
<summary>
2203     ///
Check if all types match with parameters. We can have more paramters then types (allow last RPC type to be different).
2204     ///
</summary>
2205     ///
<param name="methodParameters"></param>
2206     ///
<param name="callParameterTypes"></param>
2207     ///
<returns>If the types-array has matching parameters (of method) in the parameters array (which may be longer).</returns>
2208     
private bool CheckTypeMatch(ParameterInfo[] methodParameters, Type[] callParameterTypes)
2209     {
2210         
if (methodParameters.Length < callParameterTypes.Length)
2211         {
2212             
return false;
2213         }
2214
2215         
for (int index = 0; index < callParameterTypes.Length; index++)
2216         {
2217             Type type = methodParameters[index].ParameterType;
2218             
//todo: check metro type usage
2219             
if (callParameterTypes[index] != null && !type.Equals(callParameterTypes[index]))
2220             {
2221                 
return false;
2222             }
2223         }
2224
2225         
return true;
2226     }
2227
2228     
internal Hashtable SendInstantiate(string prefabName, Vector3 position, Quaternion rotation, int group, int[] viewIDs, object[] data, bool isGlobalObject)
2229     {
2230         
// first viewID is now also the gameobject's instantiateId
2231         
int instantiateId = viewIDs[0]; // LIMITS PHOTONVIEWS&PLAYERS
2232
2233         
//TODO: reduce hashtable key usage by using a parameter array for the various values
2234         Hashtable instantiateEvent =
new Hashtable(); // This players info is sent via ActorID
2235         instantiateEvent[(
byte)0] = prefabName;
2236
2237         
if (position != Vector3.zero)
2238         {
2239             instantiateEvent[(
byte)1] = position;
2240         }
2241
2242         
if (rotation != Quaternion.identity)
2243         {
2244             instantiateEvent[(
byte)2] = rotation;
2245         }
2246
2247         
if (group != 0)
2248         {
2249             instantiateEvent[(
byte)3] = group;
2250         }
2251
2252         
// send the list of viewIDs only if there are more than one. else the instantiateId is the viewID
2253         
if (viewIDs.Length > 1)
2254         {
2255             instantiateEvent[(
byte)4] = viewIDs; // LIMITS PHOTONVIEWS&PLAYERS
2256         }
2257
2258         
if (data != null)
2259         {
2260             instantiateEvent[(
byte)5] = data;
2261         }
2262
2263         
if (this.currentLevelPrefix > 0)
2264         {
2265             instantiateEvent[(
byte)8] = this.currentLevelPrefix; // photonview's / object's level prefix
2266         }
2267
2268         instantiateEvent[(
byte)6] = this.ServerTimeInMilliSeconds;
2269         instantiateEvent[(
byte)7] = instantiateId;
2270
2271
2272         RaiseEventOptions options =
new RaiseEventOptions();
2273         options.CachingOption = (isGlobalObject) ? EventCaching.AddToRoomCacheGlobal : EventCaching.AddToRoomCache;
2274
2275         
this.OpRaiseEvent(PunEvent.Instantiation, instantiateEvent, true, options);
2276         
return instantiateEvent;
2277     }
2278
2279     
internal GameObject DoInstantiate(Hashtable evData, PhotonPlayer photonPlayer, GameObject resourceGameObject)
2280     {
2281         
// some values always present:
2282         
string prefabName = (string)evData[(byte)0];
2283         
int serverTime = (int)evData[(byte)6];
2284         
int instantiationId = (int)evData[(byte)7];
2285
2286         Vector3 position;
2287         
if (evData.ContainsKey((byte)1))
2288         {
2289             position = (Vector3)evData[(
byte)1];
2290         }
2291         
else
2292         {
2293             position = Vector3.zero;
2294         }
2295
2296         Quaternion rotation = Quaternion.identity;
2297         
if (evData.ContainsKey((byte)2))
2298         {
2299             rotation = (Quaternion)evData[(
byte)2];
2300         }
2301
2302         
int group = 0;
2303         
if (evData.ContainsKey((byte)3))
2304         {
2305             
group = (int)evData[(byte)3];
2306         }
2307
2308         
short objLevelPrefix = 0;
2309         
if (evData.ContainsKey((byte)8))
2310         {
2311             objLevelPrefix = (
short)evData[(byte)8];
2312         }
2313
2314         
int[] viewsIDs;
2315         
if (evData.ContainsKey((byte)4))
2316         {
2317             viewsIDs = (
int[])evData[(byte)4];
2318         }
2319         
else
2320         {
2321             viewsIDs =
new int[1] { instantiationId };
2322         }
2323
2324         
object[] incomingInstantiationData;
2325         
if (evData.ContainsKey((byte)5))
2326         {
2327             incomingInstantiationData = (
object[])evData[(byte)5];
2328         }
2329         
else
2330         {
2331             incomingInstantiationData =
null;
2332         }
2333
2334         
// SetReceiving filtering
2335         
if (group != 0 && !this.allowedReceivingGroups.Contains(group))
2336         {
2337             
return null; // Ignore group
2338         }
2339
2340         
// load prefab, if it wasn't loaded before (calling methods might do this)
2341         
if (resourceGameObject == null)
2342         {
2343             
if (!NetworkingPeer.UsePrefabCache || !NetworkingPeer.PrefabCache.TryGetValue(prefabName, out resourceGameObject))
2344             {
2345                 resourceGameObject = (GameObject)Resources.Load(prefabName,
typeof(GameObject));
2346                 
if (NetworkingPeer.UsePrefabCache)
2347                 {
2348                     NetworkingPeer.PrefabCache.Add(prefabName, resourceGameObject);
2349                 }
2350             }
2351
2352             
if (resourceGameObject == null)
2353             {
2354                 Debug.LogError(
"PhotonNetwork error: Could not Instantiate the prefab [" + prefabName + "]. Please verify you have this gameobject in a Resources folder.");
2355                 
return null;
2356             }
2357         }
2358
2359         
// now modify the loaded "blueprint" object before it becomes a part of the scene (by instantiating it)
2360         PhotonView[] resourcePVs = resourceGameObject.GetPhotonViewsInChildren();
2361         
if (resourcePVs.Length != viewsIDs.Length)
2362         {
2363             
throw new Exception("Error in Instantiation! The resource's PhotonView count is not the same as in incoming data.");
2364         }
2365
2366         
for (int i = 0; i < viewsIDs.Length; i++)
2367         {
2368             
// NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below
2369             
// so we only set the viewID and instantiationId now. the instantiationData can be fetched
2370             resourcePVs[i].viewID = viewsIDs[i];
2371             resourcePVs[i].prefix = objLevelPrefix;
2372             resourcePVs[i].instantiationId = instantiationId;
2373             resourcePVs[i].isRuntimeInstantiated =
true;
2374         }
2375
2376         
this.StoreInstantiationData(instantiationId, incomingInstantiationData);
2377
2378         
// load the resource and set it's values before instantiating it:
2379         GameObject go = (GameObject)GameObject.Instantiate(resourceGameObject, position, rotation);
2380
2381         
for (int i = 0; i < viewsIDs.Length; i++)
2382         {
2383             
// NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below
2384             
// so we only set the viewID and instantiationId now. the instantiationData can be fetched
2385             resourcePVs[i].viewID =
0;
2386             resourcePVs[i].prefix = -
1;
2387             resourcePVs[i].prefixBackup = -
1;
2388             resourcePVs[i].instantiationId = -
1;
2389             resourcePVs[i].isRuntimeInstantiated =
false;
2390         }
2391
2392         
this.RemoveInstantiationData(instantiationId);
2393         
2394         
// Send OnPhotonInstantiate callback to newly created GO.
2395         
// GO will be enabled when instantiated from Prefab and it does not matter if the script is enabled or disabled.
2396         go.SendMessage(PhotonNetworkingMessage.OnPhotonInstantiate.ToString(),
new PhotonMessageInfo(photonPlayer, serverTime, null), SendMessageOptions.DontRequireReceiver);
2397         
return go;
2398     }
2399
2400     
private Dictionary<int, object[]> tempInstantiationData = new Dictionary<int, object[]>();
2401
2402     
private void StoreInstantiationData(int instantiationId, object[] instantiationData)
2403     {
2404         
// Debug.Log("StoreInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);
2405         tempInstantiationData[instantiationId] = instantiationData;
2406     }
2407
2408     
public object[] FetchInstantiationData(int instantiationId)
2409     {
2410         
object[] data = null;
2411         
if (instantiationId == 0)
2412         {
2413             
return null;
2414         }
2415
2416         tempInstantiationData.TryGetValue(instantiationId,
out data);
2417         
// Debug.Log("FetchInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);
2418         
return data;
2419     }
2420
2421     
private void RemoveInstantiationData(int instantiationId)
2422     {
2423         tempInstantiationData.Remove(instantiationId);
2424     }

2425
2426
2427     ///
<summary>
2428     ///
Destroys all Instantiates and RPCs locally and (if not localOnly) sends EvDestroy(player) and clears related events in the server buffer.
2429     ///
</summary>
2430     
public void DestroyPlayerObjects(int playerId, bool localOnly)
2431     {
2432         
if (playerId <= 0)
2433         {
2434             Debug.LogError(
"Failed to Destroy objects of playerId: " + playerId);
2435             
return;
2436         }
2437
2438         
if (!localOnly)
2439         {
2440             
// clean server's Instantiate and RPC buffers
2441             
this.OpRemoveFromServerInstantiationsOfPlayer(playerId);
2442             
this.OpCleanRpcBuffer(playerId);
2443
2444             
// send Destroy(player) to anyone else
2445             
this.SendDestroyOfPlayer(playerId);
2446         }
2447
2448         
// locally cleaning up that player's objects
2449         HashSet<GameObject> playersGameObjects =
new HashSet<GameObject>();
2450         
foreach (PhotonView view in this.photonViewList.Values)
2451         {
2452             
if (view.CreatorActorNr == playerId)
2453             {
2454                 playersGameObjects.Add(view.gameObject);
2455             }
2456         }
2457
2458         
// any non-local work is already done, so with the list of that player's objects, we can clean up (locally only)
2459         
foreach (GameObject gameObject in playersGameObjects)
2460         {
2461             
this.RemoveInstantiatedGO(gameObject, true);
2462         }
2463
2464         
// with ownership transfer, some objects might lose their owner.
2465         
// in that case, the creator becomes the owner again. every client can apply this. done below.
2466         
foreach (PhotonView view in this.photonViewList.Values)
2467         {
2468             
if (view.ownerId == playerId)
2469             {
2470                 view.ownerId = view.CreatorActorNr;
2471                 
//Debug.Log("Creator is: " + view.ownerId);
2472             }
2473         }
2474     }
2475
2476     
public void DestroyAll(bool localOnly)
2477     {
2478         
if (!localOnly)
2479         {
2480             
this.OpRemoveCompleteCache();
2481             
this.SendDestroyOfAll();
2482         }
2483
2484         
this.LocalCleanupAnythingInstantiated(true);
2485     }

2486
2487     ///
<summary>Removes GameObject and the PhotonViews on it from local lists and optionally updates remotes. GameObject gets destroyed at end.</summary>
2488     ///
<remarks>
2489     ///
This method might fail and quit early due to several tests.
2490     ///
</remarks>
2491     ///
<param name="go">GameObject to cleanup.</param>
2492     ///
<param name="localOnly">For localOnly, tests of control are skipped and the server is not updated.</param>
2493     
protected internal void RemoveInstantiatedGO(GameObject go, bool localOnly)
2494     {
2495         
if (go == null)
2496         {
2497             Debug.LogError(
"Failed to 'network-remove' GameObject because it's null.");
2498             
return;
2499         }
2500
2501         
// Don't remove the GO if it doesn't have any PhotonView
2502         PhotonView[] views = go.GetComponentsInChildren<PhotonView>(
true);
2503         
if (views == null || views.Length <= 0)
2504         {
2505             Debug.LogError(
"Failed to 'network-remove' GameObject because has no PhotonView components: " + go);
2506             
return;
2507         }
2508
2509         PhotonView viewZero = views[
0];
2510         
int creatorId = viewZero.CreatorActorNr; // creatorId of obj is needed to delete EvInstantiate (only if it's from that user)
2511         
int instantiationId = viewZero.instantiationId; // actual, live InstantiationIds start with 1 and go up
2512
2513         
// Don't remove GOs that are owned by others (unless this is the master and the remote player left)
2514         
if (!localOnly)
2515         {
2516             
if (!viewZero.isMine)
2517             {
2518                 Debug.LogError(
"Failed to 'network-remove' GameObject. Client is neither owner nor masterClient taking over for owner who left: " + viewZero);
2519                 
return;
2520             }
2521
2522             
// Don't remove the Instantiation from the server, if it doesn't have a proper ID
2523             
if (instantiationId < 1)
2524             {
2525                 Debug.LogError(
"Failed to 'network-remove' GameObject because it is missing a valid InstantiationId on view: " + viewZero + ". Not Destroying GameObject or PhotonViews!");
2526                 
return;
2527             }
2528         }
2529
2530
2531         
// cleanup instantiation (event and local list)
2532         
if (!localOnly)
2533         {
2534             
this.ServerCleanInstantiateAndDestroy(instantiationId, creatorId, viewZero.isRuntimeInstantiated); // server cleaning
2535         }
2536
2537
2538         
// cleanup PhotonViews and their RPCs events (if not localOnly)
2539         
for (int j = views.Length - 1; j >= 0; j--)
2540         {
2541             PhotonView view = views[j];
2542             
if (view == null)
2543             {
2544                 
continue;
2545             }
2546
2547             
// we only destroy/clean PhotonViews that were created by PhotonNetwork.Instantiate (and those have an instantiationId!)
2548             
if (view.instantiationId >= 1)
2549             {
2550                 
this.LocalCleanPhotonView(view);
2551             }
2552             
if (!localOnly)
2553             {
2554                 
this.OpCleanRpcBuffer(view);
2555             }
2556         }
2557
2558         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
2559             Debug.Log(
"Network destroy Instantiated GO: " + go.name);
2560
2561         GameObject.Destroy(go);
2562     }

2563
2564     ///
<summary>
2565     ///
This returns -1 if the GO could not be found in list of instantiatedObjects.
2566     ///
</summary>
2567     
public int GetInstantiatedObjectsId(GameObject go)
2568     {
2569         
int id = -1;
2570         
if (go == null)
2571         {
2572             Debug.LogError(
"GetInstantiatedObjectsId() for GO == null.");
2573             
return id;
2574         }
2575
2576         PhotonView[] pvs = go.GetPhotonViewsInChildren();
2577         
if (pvs != null && pvs.Length > 0 && pvs[0] != null)
2578         {
2579             
return pvs[0].instantiationId;
2580         }
2581
2582         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
2583             UnityEngine.Debug.Log(
"GetInstantiatedObjectsId failed for GO: " + go);
2584
2585
2586         
return id;
2587     }

2588
2589     ///
<summary>
2590     ///
Removes an instantiation event from the server's cache. Needs id and actorNr of player who instantiated.
2591     ///
</summary>
2592     
private void ServerCleanInstantiateAndDestroy(int instantiateId, int creatorId, bool isRuntimeInstantiated)
2593     {
2594         Hashtable removeFilter =
new Hashtable();
2595         removeFilter[(
byte)7] = instantiateId;
2596
2597         RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { creatorId } };
2598         
this.OpRaiseEvent(PunEvent.Instantiation, removeFilter, true, options);
2599         
//this.OpRaiseEvent(PunEvent.Instantiation, removeFilter, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);
2600
2601         Hashtable evData =
new Hashtable();
2602         evData[(
byte)0] = instantiateId;
2603         options =
null;
2604         
if (!isRuntimeInstantiated)
2605         {
2606             
// if the view got loaded with the scene, the EvDestroy must be cached (there is no Instantiate-msg which we can remove)
2607             
// reason: joining players will load the obj and have to destroy it (too)
2608             options =
new RaiseEventOptions();
2609             options.CachingOption = EventCaching.AddToRoomCacheGlobal;
2610             Debug.Log(
"Destroying GO as global. ID: " + instantiateId);
2611         }
2612         
this.OpRaiseEvent(PunEvent.Destroy, evData, true, options);
2613     }
2614
2615     
private void SendDestroyOfPlayer(int actorNr)
2616     {
2617         Hashtable evData =
new Hashtable();
2618         evData[(
byte)0] = actorNr;
2619
2620         
this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, null);
2621         
//this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);
2622     }
2623
2624     
private void SendDestroyOfAll()
2625     {
2626         Hashtable evData =
new Hashtable();
2627         evData[(
byte)0] = -1;
2628
2629
2630         
this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, null);
2631         
//this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);
2632     }
2633
2634     
private void OpRemoveFromServerInstantiationsOfPlayer(int actorNr)
2635     {
2636         
// removes all "Instantiation" events of player actorNr. this is not an event for anyone else
2637         RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNr } };
2638         
this.OpRaiseEvent(PunEvent.Instantiation, null, true, options);
2639         
//this.OpRaiseEvent(PunEvent.Instantiation, null, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);
2640     }
2641
2642     
internal protected void RequestOwnership(int viewID, int fromOwner)
2643     {
2644         Debug.Log(
"RequestOwnership(): " + viewID + " from: " + fromOwner + " Time: " + Environment.TickCount % 1000);
2645         
//PhotonNetwork.networkingPeer.OpRaiseEvent(PunEvent.OwnershipRequest, true, new int[] { viewID, fromOwner }, 0, EventCaching.DoNotCache, null, ReceiverGroup.All, 0);
2646         
this.OpRaiseEvent(PunEvent.OwnershipRequest, new int[] {viewID, fromOwner}, true, new RaiseEventOptions() { Receivers = ReceiverGroup.All }); // All sends to all via server (including self)
2647     }
2648
2649     
internal protected void TransferOwnership(int viewID, int playerID)
2650     {
2651         Debug.Log(
"TransferOwnership() view " + viewID + " to: " + playerID + " Time: " + Environment.TickCount % 1000);
2652         
//PhotonNetwork.networkingPeer.OpRaiseEvent(PunEvent.OwnershipTransfer, true, new int[] {viewID, playerID}, 0, EventCaching.DoNotCache, null, ReceiverGroup.All, 0);
2653         
this.OpRaiseEvent(PunEvent.OwnershipTransfer, new int[] { viewID, playerID }, true, new RaiseEventOptions() { Receivers = ReceiverGroup.All }); // All sends to all via server (including self)
2654     }
2655
2656     
public void LocalCleanPhotonView(PhotonView view)
2657     {
2658         view.destroyedByPhotonNetworkOrQuit =
true;
2659         
this.photonViewList.Remove(view.viewID);
2660     }
2661
2662
2663     
public PhotonView GetPhotonView(int viewID)
2664     {
2665         PhotonView result =
null;
2666         
this.photonViewList.TryGetValue(viewID, out result);
2667
2668         
if (result == null)
2669         {
2670             PhotonView[] views = GameObject.FindObjectsOfType(
typeof(PhotonView)) as PhotonView[];
2671
2672             
foreach (PhotonView view in views)
2673             {
2674                 
if (view.viewID == viewID)
2675                 {
2676                     
if (view.didAwake)
2677                     {
2678                         Debug.LogWarning(
"Had to lookup view that wasn't in photonViewList: " + view);
2679                     }
2680                     
return view;
2681                 }
2682             }
2683         }
2684
2685         
return result;
2686     }
2687
2688     
public void RegisterPhotonView(PhotonView netView)
2689     {
2690         
if (!Application.isPlaying)
2691         {
2692             
this.photonViewList = new Dictionary<int, PhotonView>();
2693             
return;
2694         }
2695
2696         
if (netView.viewID == 0)
2697         {
2698             
// don't register views with ID 0 (not initialized). they register when a ID is assigned later on
2699             Debug.Log(
"PhotonView register is ignored, because viewID is 0. No id assigned yet to: " + netView);
2700             
return;
2701         }
2702
2703         
if (this.photonViewList.ContainsKey(netView.viewID))
2704         {
2705             
// if some other view is in the list already, we got a problem. it might be undestructible. print out error
2706             
if (netView != photonViewList[netView.viewID])
2707             {
2708                 Debug.LogError(
string.Format("PhotonView ID duplicate found: {0}. New: {1} old: {2}. Maybe one wasn't destroyed on scene load?! Check for 'DontDestroyOnLoad'. Destroying old entry, adding new.", netView.viewID, netView, photonViewList[netView.viewID]));
2709             }
2710
2711             
//this.photonViewList.Remove(netView.viewID); // TODO check if we chould Destroy the GO of this view?!
2712             
this.RemoveInstantiatedGO(photonViewList[netView.viewID].gameObject, true);
2713         }
2714
2715         
// Debug.Log("adding view to known list: " + netView);
2716         
this.photonViewList.Add(netView.viewID, netView);
2717         
//Debug.LogError("view being added. " + netView); // Exit Games internal log
2718
2719         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
2720             Debug.Log(
"Registered PhotonView: " + netView.viewID);
2721     }

2722
2723     ///
// <summary>
2724     ///
// Will remove the view from list of views (by its ID).
2725     ///
// </summary>
2726     
//public void RemovePhotonView(PhotonView netView)
2727     
//{
2728     
// if (!Application.isPlaying)
2729     
// {
2730     
// this.photonViewList = new Dictionary<int, PhotonView>();
2731     
// return;
2732     
// }
2733
2734     
// //PhotonView removedView = null;
2735     
// //this.photonViewList.TryGetValue(netView.viewID, out removedView);
2736     
// //if (removedView != netView)
2737     
// //{
2738     
// // Debug.LogError("Detected two differing PhotonViews with same viewID: " + netView.viewID);
2739     
// //}
2740
2741     
// this.photonViewList.Remove(netView.viewID);
2742
2743     
// //if (this.DebugOut >= DebugLevel.ALL)
2744     
// //{
2745     
// // this.DebugReturn(DebugLevel.ALL, "Removed PhotonView: " + netView.viewID);
2746     
// //}
2747     
//}
2748
2749     ///
<summary>
2750     ///
Removes the RPCs of someone else (to be used as master).
2751     ///
This won't clean any local caches. It just tells the server to forget a player's RPCs and instantiates.
2752     ///
</summary>
2753     ///
<param name="actorNumber"></param>
2754     
public void OpCleanRpcBuffer(int actorNumber)
2755     {
2756         RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNumber } };
2757         
this.OpRaiseEvent(PunEvent.RPC, null, true, options);
2758         
//this.OpRaiseEvent(PunEvent.RPC, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);
2759     }

2760
2761     ///
<summary>
2762     ///
Instead removing RPCs or Instantiates, this removed everything cached by the actor.
2763     ///
</summary>
2764     ///
<param name="actorNumber"></param>
2765     
public void OpRemoveCompleteCacheOfPlayer(int actorNumber)
2766     {
2767         RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNumber } };
2768         
this.OpRaiseEvent(0, null, true, options);
2769         
//this.OpRaiseEvent(0, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);
2770     }
2771
2772
2773     
public void OpRemoveCompleteCache()
2774     {
2775         RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, Receivers = ReceiverGroup.MasterClient };
2776         
this.OpRaiseEvent(0, null, true, options);
2777         
//this.OpRaiseEvent(0, null, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.MasterClient); // TODO: check who gets this event?
2778     }

2779
2780     ///
This clears the cache of any player/actor who's no longer in the room (making it a simple clean-up option for a new master)
2781     
private void RemoveCacheOfLeftPlayers()
2782     {
2783         Dictionary<
byte, object> opParameters = new Dictionary<byte, object>();
2784         opParameters[ParameterCode.Code] = (
byte)0; // any event
2785         opParameters[ParameterCode.Cache] = (
byte)EventCaching.RemoveFromRoomCacheForActorsLeft; // option to clear the room cache of all events of players who left
2786
2787         
this.OpCustom((byte)OperationCode.RaiseEvent, opParameters, true, 0);
2788     }
2789
2790     
// Remove RPCs of view (if they are local player's RPCs)
2791     
public void CleanRpcBufferIfMine(PhotonView view)
2792     {
2793         
if (view.ownerId != this.mLocalActor.ID && !mLocalActor.isMasterClient)
2794         {
2795             Debug.LogError(
"Cannot remove cached RPCs on a PhotonView thats not ours! " + view.owner + " scene: " + view.isSceneView);
2796             
return;
2797         }
2798
2799         
this.OpCleanRpcBuffer(view);
2800     }

2801
2802     ///
<summary>Cleans server RPCs for PhotonView (without any further checks).</summary>
2803     
public void OpCleanRpcBuffer(PhotonView view)
2804     {
2805         Hashtable rpcFilterByViewId =
new Hashtable();
2806         rpcFilterByViewId[(
byte)0] = view.viewID;
2807
2808         RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache };
2809         
this.OpRaiseEvent(PunEvent.RPC, rpcFilterByViewId, true, options);
2810         
//this.OpRaiseEvent(PunEvent.RPC, rpcFilterByViewId, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.Others);
2811     }
2812
2813     
public void RemoveRPCsInGroup(int group)
2814     {
2815         
foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
2816         {
2817             PhotonView view = kvp.Value;
2818             
if (view.group == group)
2819             {
2820                 
this.CleanRpcBufferIfMine(view);
2821             }
2822         }
2823     }
2824
2825     
public void SetLevelPrefix(short prefix)
2826     {
2827         
this.currentLevelPrefix = prefix;
2828         
// TODO: should we really change the prefix for existing PVs?! better keep it!
2829         
//foreach (PhotonView view in this.photonViewList.Values)
2830         
//{
2831         
// view.prefix = prefix;
2832         
//}
2833     }
2834
2835     
internal void RPC(PhotonView view, string methodName, PhotonPlayer player, bool encrypt, params object[] parameters)
2836     {
2837         
if (this.blockSendingGroups.Contains(view.group))
2838         {
2839             
return; // Block sending on this group
2840         }
2841
2842         
if (view.viewID < 1) //TODO: check why 0 should be illegal
2843         {
2844             Debug.LogError(
"Illegal view ID:" + view.viewID + " method: " + methodName + " GO:" + view.gameObject.name);
2845         }
2846
2847         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
2848         {
2849             Debug.Log(
"Sending RPC \"" + methodName + "\" to player[" + player + "]");
2850         }
2851
2852
2853         
//ts: changed RPCs to a one-level hashtable as described in internal.txt
2854         Hashtable rpcEvent =
new Hashtable();
2855         rpcEvent[(
byte)0] = (int)view.viewID; // LIMITS PHOTONVIEWS&PLAYERS
2856         
if (view.prefix > 0)
2857         {
2858             rpcEvent[(
byte)1] = (short)view.prefix;
2859         }
2860         rpcEvent[(
byte)2] = this.ServerTimeInMilliSeconds;
2861
2862         
// send name or shortcut (if available)
2863         
int shortcut = 0;
2864         
if (rpcShortcuts.TryGetValue(methodName, out shortcut))
2865         {
2866             rpcEvent[(
byte)5] = (byte)shortcut; // LIMITS RPC COUNT
2867         }
2868         
else
2869         {
2870             rpcEvent[(
byte)3] = methodName;
2871         }
2872
2873         
if (parameters != null && parameters.Length > 0)
2874         {
2875             rpcEvent[(
byte) 4] = (object[]) parameters;
2876         }
2877
2878         
if (this.mLocalActor == player)
2879         {
2880             
this.ExecuteRPC(rpcEvent, player);
2881         }
2882         
else
2883         {
2884             RaiseEventOptions options =
new RaiseEventOptions() { TargetActors = new int[] { player.ID }, Encrypt = encrypt };
2885             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2886         }
2887     }

2888
2889     ///
RPC Hashtable Structure
2890     ///
(byte)0 -> (int) ViewId (combined from actorNr and actor-unique-id)
2891     ///
(byte)1 -> (short) prefix (level)
2892     ///
(byte)2 -> (int) server timestamp
2893     ///
(byte)3 -> (string) methodname
2894     ///
(byte)4 -> (object[]) parameters
2895     ///
(byte)5 -> (byte) method shortcut (alternative to name)
2896     ///
2897     ///
This is sent as event (code: 200) which will contain a sender (origin of this RPC).
2898
2899     
internal void RPC(PhotonView view, string methodName, PhotonTargets target, bool encrypt, params object[] parameters)
2900     {
2901         
if (this.blockSendingGroups.Contains(view.group))
2902         {
2903             
return; // Block sending on this group
2904         }
2905
2906         
if (view.viewID < 1)
2907         {
2908             Debug.LogError(
"Illegal view ID:" + view.viewID + " method: " + methodName + " GO:" + view.gameObject.name);
2909         }
2910
2911         
if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
2912             Debug.Log(
"Sending RPC \"" + methodName + "\" to " + target);
2913
2914
2915         
//ts: changed RPCs to a one-level hashtable as described in internal.txt
2916         Hashtable rpcEvent =
new Hashtable();
2917         rpcEvent[(
byte)0] = (int)view.viewID; // LIMITS NETWORKVIEWS&PLAYERS
2918         
if (view.prefix > 0)
2919         {
2920             rpcEvent[(
byte)1] = (short)view.prefix;
2921         }
2922         rpcEvent[(
byte)2] = this.ServerTimeInMilliSeconds;
2923
2924
2925         
// send name or shortcut (if available)
2926         
int shortcut = 0;
2927         
if (rpcShortcuts.TryGetValue(methodName, out shortcut))
2928         {
2929             rpcEvent[(
byte)5] = (byte)shortcut; // LIMITS RPC COUNT
2930         }
2931         
else
2932         {
2933             rpcEvent[(
byte)3] = methodName;
2934         }
2935
2936         
if (parameters != null && parameters.Length > 0)
2937         {
2938             rpcEvent[(
byte)4] = (object[])parameters;
2939         }
2940
2941         
// Check scoping
2942         
if (target == PhotonTargets.All)
2943         {
2944             RaiseEventOptions options =
new RaiseEventOptions() { InterestGroup = (byte)view.group, Encrypt = encrypt };
2945             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2946
2947             
// Execute local
2948             
this.ExecuteRPC(rpcEvent, this.mLocalActor);
2949         }
2950         
else if (target == PhotonTargets.Others)
2951         {
2952             RaiseEventOptions options =
new RaiseEventOptions() { InterestGroup = (byte)view.group, Encrypt = encrypt };
2953             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2954         }
2955         
else if (target == PhotonTargets.AllBuffered)
2956         {
2957             RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache, Encrypt = encrypt };
2958             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2959
2960             
// Execute local
2961             
this.ExecuteRPC(rpcEvent, this.mLocalActor);
2962         }
2963         
else if (target == PhotonTargets.OthersBuffered)
2964         {
2965             RaiseEventOptions options =
new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache, Encrypt = encrypt };
2966             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2967         }
2968         
else if (target == PhotonTargets.MasterClient)
2969         {
2970             
if (this.mMasterClient == this.mLocalActor)
2971             {
2972                 
this.ExecuteRPC(rpcEvent, this.mLocalActor);
2973             }
2974             
else
2975             {
2976                 RaiseEventOptions options =
new RaiseEventOptions() { Receivers = ReceiverGroup.MasterClient, Encrypt = encrypt };
2977                 
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2978             }
2979         }
2980         
else if (target == PhotonTargets.AllViaServer)
2981         {
2982             RaiseEventOptions options =
new RaiseEventOptions() { InterestGroup = (byte)view.group, Receivers = ReceiverGroup.All, Encrypt = encrypt };
2983             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2984             
if (PhotonNetwork.offlineMode)
2985             {
2986                 
this.ExecuteRPC(rpcEvent, this.mLocalActor);
2987             }
2988         }
2989         
else if (target == PhotonTargets.AllBufferedViaServer)
2990         {
2991             RaiseEventOptions options =
new RaiseEventOptions() { InterestGroup = (byte)view.group, Receivers = ReceiverGroup.All, CachingOption = EventCaching.AddToRoomCache, Encrypt = encrypt };
2992             
this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
2993             
if (PhotonNetwork.offlineMode)
2994             {
2995                 
this.ExecuteRPC(rpcEvent, this.mLocalActor);
2996             }
2997         }
2998         
else
2999         {
3000             Debug.LogError(
"Unsupported target enum: " + target);
3001         }
3002     }
3003
3004     
// SetReceiving
3005     
public void SetReceivingEnabled(int group, bool enabled)
3006     {
3007         
if (group <= 0)
3008         {
3009             Debug.LogError(
"Error: PhotonNetwork.SetReceivingEnabled was called with an illegal group number: " + group + ". The group number should be at least 1.");
3010             
return;
3011         }
3012
3013         
if (enabled)
3014         {
3015             
if (!this.allowedReceivingGroups.Contains(group))
3016             {
3017                 
this.allowedReceivingGroups.Add(group);
3018                 
byte[] groups = new byte[1] { (byte)group };
3019                 
this.OpChangeGroups(null, groups);
3020             }
3021         }
3022         
else
3023         {
3024             
if (this.allowedReceivingGroups.Contains(group))
3025             {
3026                 
this.allowedReceivingGroups.Remove(group);
3027                 
byte[] groups = new byte[1] { (byte)group };
3028                 
this.OpChangeGroups(groups, null);
3029             }
3030         }
3031     }
3032
3033
3034     
public void SetReceivingEnabled(int[] enableGroups, int[] disableGroups)
3035     {
3036         List<
byte> enableList = new List<byte>();
3037         List<
byte> disableList = new List<byte>();
3038
3039         
if (enableGroups != null)
3040         {
3041             
for (int index = 0; index < enableGroups.Length; index++)
3042             {
3043                 
int i = enableGroups[index];
3044                 
if (i <= 0)
3045                 {
3046                     Debug.LogError(
"Error: PhotonNetwork.SetReceivingEnabled was called with an illegal group number: " + i + ". The group number should be at least 1.");
3047                     
continue;
3048                 }
3049                 
if (!this.allowedReceivingGroups.Contains(i))
3050                 {
3051                     
this.allowedReceivingGroups.Add(i);
3052                     enableList.Add((
byte)i);
3053                 }
3054             }
3055         }
3056         
if (disableGroups != null)
3057         {
3058             
for (int index = 0; index < disableGroups.Length; index++)
3059             {
3060                 
int i = disableGroups[index];
3061                 
if (i <= 0)
3062                 {
3063                     Debug.LogError(
"Error: PhotonNetwork.SetReceivingEnabled was called with an illegal group number: " + i + ". The group number should be at least 1.");
3064                     
continue;
3065                 }
3066                 
if (enableList.Contains((byte)i))
3067                 {
3068                     Debug.LogError(
"Error: PhotonNetwork.SetReceivingEnabled disableGroups contains a group that is also in the enableGroups: " + i + ".");
3069                     
continue;
3070                 }
3071                 
if (this.allowedReceivingGroups.Contains(i))
3072                 {
3073                     
this.allowedReceivingGroups.Remove(i);
3074                     disableList.Add((
byte)i);
3075                 }
3076             }
3077         }
3078
3079         
this.OpChangeGroups(disableList.Count > 0 ? disableList.ToArray() : null, enableList.Count > 0 ? enableList.ToArray() : null); //Passing a 0 sized array != passing null
3080     }
3081
3082     
// SetSending
3083     
public void SetSendingEnabled(int group, bool enabled)
3084     {
3085         
if (!enabled)
3086         {
3087             
this.blockSendingGroups.Add(group); // can be added to HashSet no matter if already in it
3088         }
3089         
else
3090         {
3091             
this.blockSendingGroups.Remove(group);
3092         }
3093     }
3094
3095
3096     
public void SetSendingEnabled(int[] enableGroups, int[] disableGroups)
3097     {
3098         
if(enableGroups!=null){
3099             
foreach(int i in enableGroups){
3100                 
if(this.blockSendingGroups.Contains(i))
3101                     
this.blockSendingGroups.Remove(i);
3102             }
3103         }
3104         
if(disableGroups!=null){
3105             
foreach(int i in disableGroups){
3106                 
if(!this.blockSendingGroups.Contains(i))
3107                     
this.blockSendingGroups.Add(i);
3108             }
3109         }
3110     }
3111
3112
3113     
public void NewSceneLoaded()
3114     {
3115         
if (this.loadingLevelAndPausedNetwork)
3116         {
3117             
this.loadingLevelAndPausedNetwork = false;
3118             PhotonNetwork.isMessageQueueRunning =
true;
3119         }
3120         
// Debug.Log("OnLevelWasLoaded photonViewList.Count: " + photonViewList.Count); // Exit Games internal log
3121
3122         List<
int> removeKeys = new List<int>();
3123         
foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
3124         {
3125             PhotonView view = kvp.Value;
3126             
if (view == null)
3127             {
3128                 removeKeys.Add(kvp.Key);
3129             }
3130         }
3131
3132         
for (int index = 0; index < removeKeys.Count; index++)
3133         {
3134             
int key = removeKeys[index];
3135             
this.photonViewList.Remove(key);
3136         }
3137
3138         
if (removeKeys.Count > 0)
3139         {
3140             
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
3141                 Debug.Log(
"New level loaded. Removed " + removeKeys.Count + " scene view IDs from last level.");
3142         }
3143     }
3144
3145
3146     
// this is called by Update() and in Unity that means it's single threaded.
3147     
public void RunViewUpdate()
3148     {
3149         
if (!PhotonNetwork.connected || PhotonNetwork.offlineMode)
3150         {
3151             
return;
3152         }
3153
3154         
if (this.mActors == null ||
3155 #
if !PHOTON_DEVELOP
3156             
this.mActors.Count <= 1
3157 #endif
3158             )
3159         {
3160             
return; // No need to send OnSerialize messages (these are never buffered anyway)
3161         }
3162
3163         dataPerGroupReliable.Clear();
3164         dataPerGroupUnreliable.Clear();
3165
3166         
/* Format of the data hashtable:
3167          * Hasthable dataPergroup*
3168          * [(
byte)0] = this.ServerTimeInMilliSeconds;
3169          * OPTIONAL: [(
byte)1] = currentLevelPrefix;
3170          * + data
3171          */

3172
3173         
foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
3174         {
3175             PhotonView view = kvp.Value;
3176
3177             
if (view.synchronization != ViewSynchronization.Off)
3178             {
3179                 
// Fetch all sending photonViews
3180                 
if (view.isMine)
3181                 {
3182                     #
if UNITY_2_6_1 || UNITY_2_6 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5
3183                     
if (!view.gameObject.active)
3184                     {
3185                         
continue; // Only on actives
3186                     }
3187                     #
else
3188                     
if (!view.gameObject.activeInHierarchy)
3189                     {
3190                         
continue; // Only on actives
3191                     }
3192                     #endif
3193
3194                     
if (this.blockSendingGroups.Contains(view.group))
3195                     {
3196                         
continue; // Block sending on this group
3197                     }
3198
3199                     
// Run it trough its OnSerialize
3200                     Hashtable evData =
this.OnSerializeWrite(view);
3201                     
if (evData == null)
3202                     {
3203                         
continue;
3204                     }
3205
3206                     
if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed || view.mixedModeIsReliable)
3207                     {
3208                         
if (!evData.ContainsKey((byte)1) && !evData.ContainsKey((byte)2))
3209                         {
3210                             
// Everything has been removed by compression, nothing to send
3211                         }
3212                         
else
3213                         {
3214                             
if (!dataPerGroupReliable.ContainsKey(view.group))
3215                             {
3216                                 dataPerGroupReliable[view.
group] = new Hashtable();
3217                                 dataPerGroupReliable[view.
group][(byte)0] = this.ServerTimeInMilliSeconds;
3218                                 
if (currentLevelPrefix >= 0)
3219                                 {
3220                                     dataPerGroupReliable[view.
group][(byte)1] = this.currentLevelPrefix;
3221                                 }
3222                             }
3223                             Hashtable groupHashtable = dataPerGroupReliable[view.
group];
3224                             groupHashtable.Add((
short)groupHashtable.Count, evData);
3225                         }
3226                     }
3227                     
else
3228                     {
3229                         
if (!dataPerGroupUnreliable.ContainsKey(view.group))
3230                         {
3231                             dataPerGroupUnreliable[view.
group] = new Hashtable();
3232                             dataPerGroupUnreliable[view.
group][(byte)0] = this.ServerTimeInMilliSeconds;
3233                             
if (currentLevelPrefix >= 0)
3234                             {
3235                                 dataPerGroupUnreliable[view.
group][(byte)1] = this.currentLevelPrefix;
3236                             }
3237                         }
3238                         Hashtable groupHashtable = dataPerGroupUnreliable[view.
group];
3239                         groupHashtable.Add((
short)groupHashtable.Count, evData);
3240                     }
3241                 }
3242                 
else
3243                 {
3244                     
// Debug.Log(" NO OBS on " + view.name + " " + view.owner);
3245                 }
3246             }
3247             
else
3248             {
3249             }
3250         }
3251
3252         
//Send the messages: every group is send in it's own message and unreliable and reliable are split as well
3253         RaiseEventOptions options =
new RaiseEventOptions();
3254
3255 #
if PHOTON_DEVELOP
3256         options.Receivers = ReceiverGroup.All;
3257 #endif
3258
3259         
foreach (KeyValuePair<int, Hashtable> kvp in dataPerGroupReliable)
3260         {
3261             options.InterestGroup = (
byte)kvp.Key;
3262             
this.OpRaiseEvent(PunEvent.SendSerializeReliable, kvp.Value, true, options);
3263         }
3264         
foreach (KeyValuePair<int, Hashtable> kvp in dataPerGroupUnreliable)
3265         {
3266             options.InterestGroup = (
byte)kvp.Key;
3267             
this.OpRaiseEvent(PunEvent.SendSerialize, kvp.Value, false, options);
3268         }
3269     }
3270
3271     
// calls OnPhotonSerializeView (through ExecuteOnSerialize)
3272     
// the content created here is consumed by receivers in: ReadOnSerialize
3273     
private Hashtable OnSerializeWrite(PhotonView view)
3274     {
3275         PhotonStream pStream =
new PhotonStream( true, null );
3276         PhotonMessageInfo info =
new PhotonMessageInfo( this.mLocalActor, this.ServerTimeInMilliSeconds, view );
3277
3278         
// each view creates a list of values that should be sent
3279         view.SerializeView( pStream, info );
3280
3281         
if( pStream.Count == 0 )
3282         {
3283             
return null;
3284         }
3285
3286         
object[] dataArray = pStream.data.ToArray();
3287
3288         
if (view.synchronization == ViewSynchronization.UnreliableOnChange)
3289         {
3290             
if (AlmostEquals(dataArray, view.lastOnSerializeDataSent))
3291             {
3292                 
if (view.mixedModeIsReliable)
3293                 {
3294                     
return null;
3295                 }
3296
3297                 view.mixedModeIsReliable =
true;
3298                 view.lastOnSerializeDataSent = dataArray;
3299             }
3300             
else
3301             {
3302                 view.mixedModeIsReliable =
false;
3303                 view.lastOnSerializeDataSent = dataArray;
3304             }
3305         }
3306
3307         
// EVDATA:
3308         
// 0=View ID (an int, never compressed cause it's not in the data)
3309         
// 1=data of observed type (different per type of observed object)
3310         
// 2=compressed data (in this case, key 1 is empty)
3311         
// 3=list of values that are actually null (if something was changed but actually IS null)
3312         Hashtable evData =
new Hashtable();
3313         evData[(
byte)0] = (int)view.viewID;
3314         evData[(
byte)1] = dataArray; // this is the actual data (script or observed object)
3315
3316
3317         
if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
3318         {
3319             
// compress content of data set (by comparing to view.lastOnSerializeDataSent)
3320             
// the "original" dataArray is NOT modified by DeltaCompressionWrite
3321             
// if something was compressed, the evData key 2 and 3 are used (see above)
3322             
bool somethingLeftToSend = this.DeltaCompressionWrite(view, evData);
3323
3324             
// buffer the full data set (for next compression)
3325             view.lastOnSerializeDataSent = dataArray;
3326
3327             
if (!somethingLeftToSend)
3328             {
3329                 
return null;
3330             }
3331         }
3332
3333         
return evData;
3334     }

3335
3336     ///
<summary>
3337     ///
Reads updates created by OnSerializeWrite
3338     ///
</summary>
3339     
private void OnSerializeRead(Hashtable data, PhotonPlayer sender, int networkTime, short correctPrefix)
3340     {
3341         
// read view ID from key (byte)0: a int-array (PUN 1.17++)
3342         
int viewID = (int)data[(byte)0];
3343
3344
3345         PhotonView view =
this.GetPhotonView(viewID);
3346         
if (view == null)
3347         {
3348             Debug.LogWarning(
"Received OnSerialization for view ID " + viewID + ". We have no such PhotonView! Ignored this if you're leaving a room. State: " + this.State);
3349             
return;
3350         }
3351
3352         
if (view.prefix > 0 && correctPrefix != view.prefix)
3353         {
3354             Debug.LogError(
"Received OnSerialization for view ID " + viewID + " with prefix " + correctPrefix + ". Our prefix is " + view.prefix);
3355             
return;
3356         }
3357
3358         
// SetReceiving filtering
3359         
if (view.group != 0 && !this.allowedReceivingGroups.Contains(view.group))
3360         {
3361             
return; // Ignore group
3362         }
3363
3364
3365         
if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
3366         {
3367             
if (!this.DeltaCompressionRead(view, data))
3368             {
3369                 
// Skip this packet as we haven't got received complete-copy of this view yet.
3370                 
if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
3371                     Debug.Log(
"Skipping packet for " + view.name + " [" + view.viewID + "] as we haven't received a full packet for delta compression yet. This is OK if it happens for the first few frames after joining a game.");
3372                 
return;
3373             }
3374
3375             
// store last received for delta-compression usage
3376             view.lastOnSerializeDataReceived = data[(
byte)1] as object[];
3377         }
3378
3379         
if (sender.ID != view.ownerId)
3380         {
3381             
if (!view.isSceneView || !sender.isMasterClient)
3382             {
3383                 
// obviously the owner changed and we didn't yet notice.
3384                 Debug.Log(
"Adjusting owner to sender of updates. From: " + view.ownerId + " to: " + sender.ID);
3385                 view.ownerId = sender.ID;
3386             }
3387         }
3388
3389         
object[] contents = data[(byte)1] as object[];
3390         PhotonStream pStream =
new PhotonStream(false, contents);
3391         PhotonMessageInfo info =
new PhotonMessageInfo(sender, networkTime, view);
3392
3393         view.DeserializeView( pStream, info );
3394     }
3395
3396     
private bool AlmostEquals(object[] lastData, object[] currentContent)
3397     {
3398         
if (lastData == null && currentContent == null)
3399         {
3400             
return true;
3401         }
3402
3403         
if (lastData == null || currentContent == null || (lastData.Length != currentContent.Length))
3404         {
3405             
return false;
3406         }
3407
3408         
for (int index = 0; index < currentContent.Length; index++)
3409         {
3410             
object newObj = currentContent[index];
3411             
object oldObj = lastData[index];
3412             
if (!this.ObjectIsSameWithInprecision(newObj, oldObj))
3413             {
3414                 
return false;
3415             }
3416         }
3417
3418         
return true;
3419     }

3420
3421     ///
<summary>
3422     ///
Compares the new data with previously sent data and skips values that didn't change.
3423     ///
</summary>
3424     ///
<returns>True if anything has to be sent, false if nothing new or no data</returns>
3425     
private bool DeltaCompressionWrite(PhotonView view, Hashtable data)
3426     {
3427         
if (view.lastOnSerializeDataSent == null)
3428         {
3429             
return true; // all has to be sent
3430         }
3431
3432         
// We can compress as we sent a full update previously (readers can re-use previous values)
3433         
object[] lastData = view.lastOnSerializeDataSent;
3434         
object[] currentContent = data[(byte)1] as object[];
3435
3436         
if (currentContent == null)
3437         {
3438             
// no data to be sent
3439             
return false;
3440         }
3441
3442         
if (lastData.Length != currentContent.Length)
3443         {
3444             
// if new data isn't same length as before, we send the complete data-set uncompressed
3445             
return true;
3446         }
3447
3448         
object[] compressedContent = new object[currentContent.Length];
3449         
int compressedValues = 0;
3450
3451         List<
int> valuesThatAreChangedToNull = new List<int>();
3452         
for (int index = 0; index < compressedContent.Length; index++)
3453         {
3454             
object newObj = currentContent[index];
3455             
object oldObj = lastData[index];
3456             
if (this.ObjectIsSameWithInprecision(newObj, oldObj))
3457             {
3458                 
// compress (by using null, instead of value, which is same as before)
3459                 compressedValues++;
3460                 
// compressedContent[index] is already null (initialized)
3461             }
3462             
else
3463             {
3464                 compressedContent[index] = currentContent[index];
3465
3466                 
// value changed, we don't replace it with null
3467                 
// new value is null (like a compressed value): we have to mark it so it STAYS null instead of being replaced with previous value
3468                 
if (newObj == null)
3469                 {
3470                     valuesThatAreChangedToNull.Add(index);
3471                 }
3472             }
3473         }
3474
3475         
// Only send the list of compressed fields if we actually compressed 1 or more fields.
3476         
if (compressedValues > 0)
3477         {
3478             data.Remove((
byte)1); // remove the original data (we only send compressed data)
3479
3480             
if (compressedValues == currentContent.Length)
3481             {
3482                 
// all values are compressed to null, we have nothing to send
3483                 
return false;
3484             }
3485
3486             data[(
byte)2] = compressedContent; // current, compressted data is moved to key 2 to mark it as compressed
3487             
if (valuesThatAreChangedToNull.Count > 0)
3488             {
3489                 data[(
byte)3] = valuesThatAreChangedToNull.ToArray(); // data that is actually null (not just cause we didn't want to send it)
3490             }
3491         }
3492
3493         
return true; // some data was compressed but we need to send something
3494     }

3495
3496     ///
<summary>
3497     ///
reads incoming messages created by "OnSerialize"
3498     ///
</summary>
3499     
private bool DeltaCompressionRead(PhotonView view, Hashtable data)
3500     {
3501         
if (data.ContainsKey((byte)1))
3502         {
3503             
// we have a full list of data (cause key 1 is used), so return "we have uncompressed all"
3504             
return true;
3505         }
3506
3507         
// Compression was applied as data[(byte)2] exists (this is the data with some fields being compressed to null)
3508         
// now we also need a previous "full" list of values to restore values that are null in this msg
3509         
if (view.lastOnSerializeDataReceived == null)
3510         {
3511             
return false; // We dont have a full match yet, we cannot work with missing values: skip this message
3512         }
3513
3514         
object[] compressedContents = data[(byte)2] as object[];
3515         
if (compressedContents == null)
3516         {
3517             
// despite expectation, there is no compressed data in this msg. shouldn't happen. just a null check
3518             
return false;
3519         }
3520
3521         
int[] indexesThatAreChangedToNull = data[(byte)3] as int[];
3522         
if (indexesThatAreChangedToNull == null)
3523         {
3524             indexesThatAreChangedToNull =
new int[0];
3525         }
3526
3527         
object[] lastReceivedData = view.lastOnSerializeDataReceived;
3528         
for (int index = 0; index < compressedContents.Length; index++)
3529         {
3530             
if (compressedContents[index] == null && !indexesThatAreChangedToNull.Contains(index))
3531             {
3532                 
// we replace null values in this received msg unless a index is in the "changed to null" list
3533                 
object lastValue = lastReceivedData[index];
3534                 compressedContents[index] = lastValue;
3535             }
3536         }
3537
3538         data[(
byte)1] = compressedContents; // compressedContents are now uncompressed...
3539         
return true;
3540     }

3541
3542     ///
<summary>
3543     ///
Returns true if both objects are almost identical.
3544     ///
Used to check whether two objects are similar enough to skip an update.
3545     ///
</summary>
3546     
bool ObjectIsSameWithInprecision(object one, object two)
3547     {
3548         
if (one == null || two == null)
3549         {
3550             
return one == null && two == null;
3551         }
3552
3553         
if (!one.Equals(two))
3554         {
3555             
// if A is not B, lets check if A is almost B
3556             
if (one is Vector3)
3557             {
3558                 Vector3 a = (Vector3)one;
3559                 Vector3 b = (Vector3)two;
3560                 
if (a.AlmostEquals(b, PhotonNetwork.precisionForVectorSynchronization))
3561                 {
3562                     
return true;
3563                 }
3564             }
3565             
else if (one is Vector2)
3566             {
3567                 Vector2 a = (Vector2)one;
3568                 Vector2 b = (Vector2)two;
3569                 
if (a.AlmostEquals(b, PhotonNetwork.precisionForVectorSynchronization))
3570                 {
3571                     
return true;
3572                 }
3573             }
3574             
else if (one is Quaternion)
3575             {
3576                 Quaternion a = (Quaternion)one;
3577                 Quaternion b = (Quaternion)two;
3578                 
if (a.AlmostEquals(b, PhotonNetwork.precisionForQuaternionSynchronization))
3579                 {
3580                     
return true;
3581                 }
3582             }
3583             
else if (one is float)
3584             {
3585                 
float a = (float)one;
3586                 
float b = (float)two;
3587                 
if (a.AlmostEquals(b, PhotonNetwork.precisionForFloatSynchronization))
3588                 {
3589                     
return true;
3590                 }
3591             }
3592
3593             
// one does not equal two
3594             
return false;
3595         }
3596
3597         
return true;
3598     }
3599
3600     
internal protected static bool GetMethod(MonoBehaviour monob, string methodType, out MethodInfo mi)
3601     {
3602         mi =
null;
3603
3604         
if (monob == null || string.IsNullOrEmpty(methodType))
3605         {
3606             
return false;
3607         }
3608
3609         List<MethodInfo> methods = SupportClass.GetMethods(monob.GetType(),
null);
3610         
for (int index = 0; index < methods.Count; index++)
3611         {
3612             MethodInfo methodInfo = methods[index];
3613             
if (methodInfo.Name.Equals(methodType))
3614             {
3615                 mi = methodInfo;
3616                 
return true;
3617             }
3618         }
3619
3620         
return false;
3621     }

3622
3623     ///
<summary>Internally used to flag if the message queue was disabled by a "scene sync" situation (to re-enable it).</summary>
3624     
internal protected bool loadingLevelAndPausedNetwork = false;
3625
3626     ///
<summary>For automatic scene syncing, the loaded scene is put into a room property. This is the name of said prop.</summary>
3627     
internal protected const string CurrentSceneProperty = "curScn";
3628
3629     ///
<summary>Internally used to detect the current scene and load it if PhotonNetwork.automaticallySyncScene is enabled.</summary>
3630     
internal protected void LoadLevelIfSynced()
3631     {
3632         
if (!PhotonNetwork.automaticallySyncScene || PhotonNetwork.isMasterClient || PhotonNetwork.room == null)
3633         {
3634             
return;
3635         }
3636
3637         
// check if "current level" is set in props
3638         
if (!PhotonNetwork.room.customProperties.ContainsKey(NetworkingPeer.CurrentSceneProperty))
3639         {
3640             
return;
3641         }
3642
3643         
// if loaded level is not the one defined my master in props, load that level
3644         
object sceneId = PhotonNetwork.room.customProperties[NetworkingPeer.CurrentSceneProperty];
3645         
if (sceneId is int)
3646         {
3647             
if (Application.loadedLevel != (int)sceneId)
3648                 PhotonNetwork.LoadLevel((
int)sceneId);
3649         }
3650         
else if (sceneId is string)
3651         {
3652             
if (Application.loadedLevelName != (string)sceneId)
3653                 PhotonNetwork.LoadLevel((
string)sceneId);
3654         }
3655     }
3656
3657     
protected internal void SetLevelInPropsIfSynced(object levelId)
3658     {
3659         
if (!PhotonNetwork.automaticallySyncScene || !PhotonNetwork.isMasterClient || PhotonNetwork.room == null)
3660         {
3661             
return;
3662         }
3663         
if (levelId == null)
3664         {
3665             Debug.LogError(
"Parameter levelId can't be null!");
3666             
return;
3667         }
3668
3669         
// check if "current level" is already set in props
3670         
if (PhotonNetwork.room.customProperties.ContainsKey(NetworkingPeer.CurrentSceneProperty))
3671         {
3672             
object levelIdInProps = PhotonNetwork.room.customProperties[NetworkingPeer.CurrentSceneProperty];
3673             
if (levelIdInProps is int && Application.loadedLevel == (int)levelIdInProps)
3674             {
3675                 
return;
3676             }
3677             
if (levelIdInProps is string && Application.loadedLevelName.Equals((string)levelIdInProps))
3678             {
3679                 
return;
3680             }
3681         }
3682
3683         
// current level is not yet in props, so this client has to set it
3684         Hashtable setScene =
new Hashtable();
3685         
if (levelId is int) setScene[NetworkingPeer.CurrentSceneProperty] = (int)levelId;
3686         
else if (levelId is string) setScene[NetworkingPeer.CurrentSceneProperty] = (string)levelId;
3687         
else Debug.LogError("Parameter levelId must be int or string!");
3688
3689         PhotonNetwork.room.SetCustomProperties(setScene);
3690         
this.SendOutgoingCommands(); // send immediately! because: in most cases the client will begin to load and not send for a while
3691     }
3692
3693     
public void SetApp(string appId, string gameVersion)
3694     {
3695         
this.mAppId = appId.Trim();
3696
3697         
if (!string.IsNullOrEmpty(gameVersion))
3698         {
3699             
this.mAppVersion = gameVersion.Trim();
3700         }
3701     }
3702
3703
3704     
public bool WebRpc(string uriPath, object parameters)
3705     {
3706         Dictionary<
byte, object> opParameters = new Dictionary<byte, object>();
3707         opParameters.Add(ParameterCode.UriPath, uriPath);
3708         opParameters.Add(ParameterCode.WebRpcParameters, parameters);
3709
3710         
return this.OpCustom(OperationCode.WebRpc, opParameters, true);
3711
3712     }
3713 }


--------------------------------------------------------------------------------------------------------------------

Part of: Photon Unity Networking (PUN)

--------------------------------------------------------------------------------------------------------------------

Implements Photon LoadBalancing used in PUN.

This class is used internally by PhotonNetwork and not intended as public API.

The GameVersion as set in the Connect-methods.

Combination of GameVersion+"_"+PunVersion. Separates players per app by version.

Contains the AppId for the Photon Cloud (ignored by Photon Servers).

A user's authentication values used during connect for Custom Authentication with Photon (and a custom servicecommunity).

Set these before calling Connect if you want custom authentication.

Only when in a room

"public" access to the current game - is null unless a room is joined on a gameserver

keeps the custom properties, gameServer address and anything else about the room we want to get into

Stat value: Count of players on Master (looking for rooms)

Stat value: Count of Rooms

Stat value: Count of Players in rooms

internal protected Dictionary photonViewList = new Dictionary(); TODO: make private again

private readonly Dictionary dataPerGroupReliable = new Dictionary(); only used in RunViewUpdate()

private readonly Dictionary dataPerGroupUnreliable = new Dictionary(); only used in RunViewUpdate()

private readonly Dictionary rpcShortcuts; lookup "table" for the index (shortcut) of an RPC name

The server this client is currently connected or connecting to.

True if this client uses a NameServer to get the Master Server address.

Name Server Address that this client uses. You can use the default values and usually won't have to set this value.

public string NameServerAddressHttp = "http:ns.exitgamescloud.com:80photonn";

private static readonly Dictionary ProtocolToNameServerPort = new Dictionary() { {ConnectionProtocol.Udp, 5058}, {ConnectionProtocol.Tcp, 4533} }; , { ConnectionProtocol.RHttp, 6063 } };

A list of region names for the Photon Cloud. Set by the result of OpGetRegions().

Put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available.

The cloud region this client connects to. Set by ConnectToRegionMaster().

Internally used to check if a "Secret" is available to use. Sent by Photon Cloud servers, it simplifies authentication when switching servers.

TODO: comment in the code again, when the new auth-workflow is available in the Cloud

return false; this.CustomAuthenticationValues != null && !String.IsNullOrEmpty(this.CustomAuthenticationValues.Secret);

Internally used to trigger OpAuthenticate when encryption was established after a connect.

this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)

PhotonHandler.PingImplementation = typeof(PingWindowsStore); but for ping, we have to set the implementation explicitly to Win 8 StorePhone

#pragma warning disable 0162 the library variant defines if we should use PUN's SocketUdp variant (at all)

this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)

don't set the field directly! the listener is passed on to other classes, which get updated by the property set method

RPC shortcut lookup creation (from list of RPCs, which is updated by Editor scripts)

connect might fail, if the DNS name can't be resolved or if no network connection is available

Connects to the NameServer for Photon Cloud, where a region and server list can be obtained.

If the workflow was started or failed right away.

Connects you to a specific region's Master Server, using the Name Server to find the IP.

If the operation could be sent. If false, no operation was sent.

Debug.Log("Server to connect to: "+ address + " settings protocol: " + PhotonNetwork.PhotonServerSettings.Protocol);

While on the NameServer, this gets you the list of regional servers (short names and their IPs to ping them).

If the operation could be sent. If false, no operation was sent (e.g. while not connected to the NameServer).

Complete disconnect from photon (and the open master OR game server)

this.LeftRoomCleanup();

this.LeftLobbyCleanup();

Internally used only. Triggers OnStateChange with "Disconnect" in next dispatch which is the signal to re-connect (if at all).

LeftLobbyCleanup();

this.LeftRoomCleanup();

Called at disconnectleavelobby etc. This CAN also be called when we are not in a lobby (e.g. disconnect from room)

Calls callback method OnLeftLobby if this client was in a lobby initially. Clears the lobby's game lists.

Called when "this client" left a room to clean up.

when leaving a room, we clean up depending on that room's settings.

Cleanup all network objects (all spawned PhotonViews, local and remote)

PhotonNetwork.manuallyAllocatedViewIds = new List(); filled and easier to replace completely

Cleans up anything that was instantiated in-game (not loaded with the scene).

Destroy GO's (if we should)

Fill list with Instantiated objects

instantiatedGos.Add(view.gameObject); HashSet keeps each object only once

photonViewList is cleared of anything instantiated (so scene items are left inside)

any other lists can be

this.tempInstantiationData.Clear(); should be empty but to be safe we clear (no new list needed)

gameID can be null (optional). The server assigns a unique name if no name is set

joins a room and sets your current username as custom actorproperty (will broadcast that)

Debug.LogWarning("ReadoutProperties gameProperties: " + gameProperties.ToStringFull() + " pActorProperties: " + pActorProperties.ToStringFull() + " targetActorNr: " + targetActorNr);

read game properties and cache them locally

this.LoadLevelIfSynced(); will load new scene if sceneName was changed

we have a single entry in the pActorProperties with one

user's name

targets MUST exist before you set properties

in this case, we've got a key-value pair per actor (each

value is a hashtable with the actor's properties then)

Resets the PhotonView "lastOnSerializeDataSent" so that "OnReliable" synched PhotonViews send a complete state to new clients (if the state doesnt change, no messages would be send otherwise!).

Note that due to this reset, ALL other players will receive the full OnSerialize.

Called when the event Leave (of some other player) arrived.

Cleans game objects, views locally. The master will also clean the

ID of player who left.

actorNr is fetched out of event above

having a new master before calling destroy for the leaving player is important!

so we elect a new masterclient and ignore the leaving player (who is still in playerlists).

destroy objects & buffered messages

finally, send notification (the playerList and masterclient are now updated)

Picks the new master client from player list, if the current Master is leaving (leavingPlayerId) or if no master was assigned so far.

The ignored player is the one who's leaving and should not become master (again). Pass -1 to select any player from the list.

return early if SOME player (leavingId > 0) is leaving AND it's NOT the current master

picking the player with lowest ID (longest in game).

keys in mActors are their actorNumbers

make a callback ONLY when a playerMaster left

Returns the lowest player.ID - used for Master Client picking.

SendMonoMessage(PhotonNetworkingMessage.OnMasterClientSwitched, this.mMasterClient); we only callback when an actual change is done

this means, the join on the gameServer is sent (with an outdated name). send the new when in game

the local player's actor-properties are not returned in join-result. add this player to the list

the mono message for this is sent at another place

NetworkingPeer.OpCreateGame

this.mRoomToEnterLobby = typedLobby ?? ((this.insideLobby) ? this.lobby : null); use given lobby, or active lobby (if any active) or none

NetworkingPeer.OpJoinRoom

roomOptions and typedLobby will be null, unless createIfNotExists is true

this.mRoomToEnterLobby = typedLobby ?? ((this.insideLobby) ? this.lobby : null); use given lobby, or active lobby (if any active) or none

NetworkingPeer.OpJoinRandomRoom

this override just makes sure we have a mRoomToGetInto, even if it's blank (the properties provided in this method are filters. they are not set when we join the game)

this.mRoomToEnterLobby = null; join random never stores the lobby. the following join will not affect the room lobby

if typedLobby is null, the server will automatically use the active lobby or default, which is what we want anyways

Operation Leave will exit any current room.

This also happens when you disconnect from the server.

Disconnect might be a step less if you don't want to create a new room on the same server.

extra logging for error debugging (helping developers with a bit of automated analysis)

use the "secret" or "token" whenever we get it. doesn't really matter if it's in AuthResponse.

this.DebugReturn(DebugLevel.ERROR, "Server returned secret. Created CustomAuthenticationValues.");

PeerState oldState = this.State;

on the NameServer, authenticate returns the MasterServer address for a region and we hop off to there

if we just "join" the game, do so. if we wanted to "create the room on demand", we have to send this to the game server as well.

on the game server, we have to apply the room properties that were chosen for creation of the room, so we use this.mRoomToGetInto

Debug.Log("GetRegions returned: " + operationResponse.ToStringFull());

PUN assumes you fetch the name-server's list of regions to ping them

is only sent by the server's response, if it has not been

sent with the client's request before!

happens only on master. on gameserver, this is a regular join (we don't need to find a random game again)

the operation OpJoinRandom either fails (with returncode 8) or returns game-to-join information

this.mListener.joinLobbyReturn();

this.LeftLobbyCleanup(); will set insideLobby = false

this.mListener.setPropertiesReturn(returnCode, debugMsg);

RemoveByteTypedPropertyKeys(actorProperties, false);

RemoveByteTypedPropertyKeys(gameProperties, false);

this.mListener.getPropertiesReturn(gameProperties, actorProperties, returnCode, debugMsg);

this usually doesn't give us a result. only if the caching is affected the server will send one.

any of the lists is null and shouldn't. print a error

this.friendListTimestamp = 1; makes sure the timestamp is not accidentally 0

Contains the list of names of friends to look up their state on the server.

Age of friend list info (in milliseconds). It's 0 until a friend list is fetched.

Internal flag to know if the client currently fetches a friend list.

Request the rooms and online status for a list of friends. All client must set a unique username via PlayerName property. The result is available in this.Friends.

Used on Master Server to find the rooms played by a selected list of users.

The result will be mapped to LoadBalancingClient.Friends when available.

The list is initialized by OpFindFriends on first use (before that, it is null).

Users identify themselves by setting a PlayerName in the LoadBalancingClient instance.

This in turn will send the name in OpAuthenticate after each connect (to master and game servers).

Note: Changing a player's name doesn't make sense when using a friend list.

The list of usernames must be fetched from some other source (not provided by Photon).

Internal:

The server response includes 2 arrays of info (each index matching a friend from the request):

ParameterCode.FindFriendsResponseOnlineList = bool[] of online states

ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)

Array of friend's names (make sure they are unique).

If the operation could be sent (requires connection, only one request is allowed at any time). Always false in offline mode.

return false; fetching friends currently, so don't do it again (avoid changing the list while fetching friends)

this.CustomAuthenticationValues.Secret = null; when connecting to NameServer, invalidate any auth values

this.IsInitialConnect = false; after handling potential initial-connect issues with special messages, we are now sure we can reach a server

this.EstablishEncryption(); always enable encryption

if we have a token we don't have to wait for encryption (it is encrypted anyways, so encryption is just optional later on)

on nameserver, the "process" is stopped here, so the developergame can either get regions or authenticate with a specific region

this client is not setup to connect to a default region. find out which regions there are!

we might need to authenticate automatically now, so the client can do anything at all

once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)

this.OpAuthenticate(this.mAppId, this.mAppVersionPun, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion.ToString()); TODO: check if there are alternatives

this.CustomAuthenticationValues.Secret = null; invalidate any custom auth secrets

this.State = global::PeerState.PeerCreated; if we set another state here, we could keep clients from connecting in OnDisconnectedFromPhoton right here.

this.CustomAuthenticationValues.Secret = null; invalidate any custom auth secrets

this.CustomAuthenticationValues.Secret = null; invalidate any custom auth secrets

this.mListener.clientErrorReturn(statusCode);

this.mListener.warningReturn(statusCode);

TCP "routing" is an option of Photon that's not currently needed (or supported) by PUN

case StatusCode.TcpRouterResponseOk:

break;

case StatusCode.TcpRouterResponseEndpointUnknown:

case StatusCode.TcpRouterResponseNodeIdUnknown:

case StatusCode.TcpRouterResponseNodeNotReady:

this.DebugReturn(DebugLevel.ERROR, "Unexpected router response: " + statusCode);

break;

this.mListener.serverErrorReturn(statusCode.value());

else

{

the actor sending this event is not in actorlist. this is usually no problem

if (photonEvent.Code != (byte)LiteOpCode.Join)

{

Debug.LogWarning("Received event, but we do not have this actor: " + actorNr);

}

}

a takeover is successful automatically, if taken from current owner

not used anymore

Debug.LogInfo("Received stats!");

actorNr is fetched out of event above

this.ResetPhotonViewsOnSerialize(); This sets the correct OnSerializeState for Reliable OnSerialize

in this player's 'own' join event, we get a complete list of players in the room, so check if we know all players

joinWithCreateOnDemand can turn an OpJoin into creating the room. Then actorNumber is 1 and callback: OnCreatedRoom()

SendMonoMessage(PhotonNetworkingMessage.OnJoinedRoom); Always send OnJoinedRoom

ts: each event now contains a single RPC. execute this

Debug.Log("Ev RPC from: " + originatingPlayer);

Debug.Log(serializeData.ToStringFull());

MasterClient "requests" a disconnection from us

Debug.Log("Ev Destroy for viewId: " + instantiationId + " sent by owner: " + (instantiationId PhotonNetwork.MAX_VIEW_IDS == actorNr) + " this client is owner: " + (instantiationId PhotonNetwork.MAX_VIEW_IDS == this.mLocalActor.ID));

actorNr might be null. it is fetched out of event on top of method

Hashtable eventContent = (Hashtable) photonEvent[ParameterCode.Data];

this.mListener.customEventAction(actorNr, eventCode, eventContent);

this.OpRaiseEvent(PunEvent.VacantViewIds, true, vacantViews.ToArray());

PHOTONVIEWRPC related

Executes a received RPC event

ts: updated with "flat" event data

int netViewID = (int)rpcData[(byte)0]; LIMITS PHOTONVIEWS&PLAYERS

int otherSidePrefix = 0; by default, the prefix is 0 (and this is not being sent)

int rpcIndex = (byte)rpcData[(byte)5]; LIMITS RPC COUNT

Get method name

SetReceiving filtering

return; Ignore group

MonoBehaviour[] mbComponents = photonNetview.GetComponents(); NOTE: we could possibly also cache MonoBehaviours per view?!

Get [RPC] methods from cache

Check cache for valid methodname+arguments

Normal, PhotonNetworkMessage left out

Check for PhotonNetworkMessage being the last

Error handling

Check if all types match with parameters. We can have more paramters then types (allow last RPC type to be different).

If the types-array has matching parameters (of method) in the parameters array (which may be longer).

todo: check metro type usage

first viewID is now also the gameobject's instantiateId

int instantiateId = viewIDs[0]; LIMITS PHOTONVIEWS&PLAYERS

TODO: reduce hashtable key usage by using a parameter array for the various values

Hashtable instantiateEvent = new Hashtable(); This players info is sent via ActorID

send the list of viewIDs only if there are more than one. else the instantiateId is the viewID

instantiateEvent[(byte)4] = viewIDs; LIMITS PHOTONVIEWS&PLAYERS

instantiateEvent[(byte)8] = this.currentLevelPrefix; photonview's object's level prefix

some values always present:

SetReceiving filtering

return null; Ignore group

load prefab, if it wasn't loaded before (calling methods might do this)

now modify the loaded "blueprint" object before it becomes a part of the scene (by instantiating it)

NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below

so we only set the viewID and instantiationId now. the instantiationData can be fetched

load the resource and set it's values before instantiating it:

NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below

so we only set the viewID and instantiationId now. the instantiationData can be fetched

Send OnPhotonInstantiate callback to newly created GO.

GO will be enabled when instantiated from Prefab and it does not matter if the script is enabled or disabled.

Debug.Log("StoreInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);

Debug.Log("FetchInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);

Destroys all Instantiates and RPCs locally and (if not localOnly) sends EvDestroy(player) and clears related events in the server buffer.

clean server's Instantiate and RPC buffers

send Destroy(player) to anyone else

locally cleaning up that player's objects

any non-local work is already done, so with the list of that player's objects, we can clean up (locally only)

with ownership transfer, some objects might lose their owner.

in that case, the creator becomes the owner again. every client can apply this. done below.

Debug.Log("Creator is: " + view.ownerId);

Removes GameObject and the PhotonViews on it from local lists and optionally updates remotes. GameObject gets destroyed at end.

This method might fail and quit early due to several tests.

GameObject to cleanup.

For localOnly, tests of control are skipped and the server is not updated.

Don't remove the GO if it doesn't have any PhotonView

int creatorId = viewZero.CreatorActorNr; creatorId of obj is needed to delete EvInstantiate (only if it's from that user)

int instantiationId = viewZero.instantiationId; actual, live InstantiationIds start with 1 and go up

Don't remove GOs that are owned by others (unless this is the master and the remote player left)

Don't remove the Instantiation from the server, if it doesn't have a proper ID

cleanup instantiation (event and local list)

this.ServerCleanInstantiateAndDestroy(instantiationId, creatorId, viewZero.isRuntimeInstantiated); server cleaning

cleanup PhotonViews and their RPCs events (if not localOnly)

we only destroyclean PhotonViews that were created by PhotonNetwork.Instantiate (and those have an instantiationId!)

This returns -1 if the GO could not be found in list of instantiatedObjects.

Removes an instantiation event from the server's cache. Needs id and actorNr of player who instantiated.

this.OpRaiseEvent(PunEvent.Instantiation, removeFilter, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);

if the view got loaded with the scene, the EvDestroy must be cached (there is no Instantiate-msg which we can remove)

reason: joining players will load the obj and have to destroy it (too)

this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);

this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);

removes all "Instantiation" events of player actorNr. this is not an event for anyone else

this.OpRaiseEvent(PunEvent.Instantiation, null, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);

PhotonNetwork.networkingPeer.OpRaiseEvent(PunEvent.OwnershipRequest, true, new int[] { viewID, fromOwner }, 0, EventCaching.DoNotCache, null, ReceiverGroup.All, 0);

this.OpRaiseEvent(PunEvent.OwnershipRequest, new int[] {viewID, fromOwner}, true, new RaiseEventOptions() { Receivers = ReceiverGroup.All }); All sends to all via server (including self)

PhotonNetwork.networkingPeer.OpRaiseEvent(PunEvent.OwnershipTransfer, true, new int[] {viewID, playerID}, 0, EventCaching.DoNotCache, null, ReceiverGroup.All, 0);

this.OpRaiseEvent(PunEvent.OwnershipTransfer, new int[] { viewID, playerID }, true, new RaiseEventOptions() { Receivers = ReceiverGroup.All }); All sends to all via server (including self)

don't register views with ID 0 (not initialized). they register when a ID is assigned later on

if some other view is in the list already, we got a problem. it might be undestructible. print out error

this.photonViewList.Remove(netView.viewID); TODO check if we chould Destroy the GO of this view?!

Debug.Log("adding view to known list: " + netView);

Debug.LogError("view being added. " + netView); Exit Games internal log

Will remove the view from list of views (by its ID).

public void RemovePhotonView(PhotonView netView)

{

if (!Application.isPlaying)

{

this.photonViewList = new Dictionary();

return;

}

PhotonView removedView = null;

this.photonViewList.TryGetValue(netView.viewID, out removedView);

if (removedView != netView)

{

Debug.LogError("Detected two differing PhotonViews with same viewID: " + netView.viewID);

}

this.photonViewList.Remove(netView.viewID);

if (this.DebugOut >= DebugLevel.ALL)

{

this.DebugReturn(DebugLevel.ALL, "Removed PhotonView: " + netView.viewID);

}

}

Removes the RPCs of someone else (to be used as master).

This won't clean any local caches. It just tells the server to forget a player's RPCs and instantiates.

this.OpRaiseEvent(PunEvent.RPC, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);

Instead removing RPCs or Instantiates, this removed everything cached by the actor.

this.OpRaiseEvent(0, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);

this.OpRaiseEvent(0, null, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.MasterClient); TODO: check who gets this event?

This clears the cache of any playeractor who's no longer in the room (making it a simple clean-up option for a new master)

opParameters[ParameterCode.Code] = (byte)0; any event

opParameters[ParameterCode.Cache] = (byte)EventCaching.RemoveFromRoomCacheForActorsLeft; option to clear the room cache of all events of players who left

Remove RPCs of view (if they are local player's RPCs)

Cleans server RPCs for PhotonView (without any further checks).

this.OpRaiseEvent(PunEvent.RPC, rpcFilterByViewId, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.Others);

TODO: should we really change the prefix for existing PVs?! better keep it!

foreach (PhotonView view in this.photonViewList.Values)

{

view.prefix = prefix;

}

return; Block sending on this group

if (view.viewID < 1) TODO: check why 0 should be illegal

ts: changed RPCs to a one-level hashtable as described in internal.txt

rpcEvent[(byte)0] = (int)view.viewID; LIMITS PHOTONVIEWS&PLAYERS

send name or shortcut (if available)

rpcEvent[(byte)5] = (byte)shortcut; LIMITS RPC COUNT

RPC Hashtable Structure

(byte)0 -> (int) ViewId (combined from actorNr and actor-unique-id)

(byte)1 -> (short) prefix (level)

(byte)2 -> (int) server timestamp

(byte)3 -> (string) methodname

(byte)4 -> (object[]) parameters

(byte)5 -> (byte) method shortcut (alternative to name)

This is sent as event (code: 200) which will contain a sender (origin of this RPC).

return; Block sending on this group

ts: changed RPCs to a one-level hashtable as described in internal.txt

rpcEvent[(byte)0] = (int)view.viewID; LIMITS NETWORKVIEWS&PLAYERS

send name or shortcut (if available)

rpcEvent[(byte)5] = (byte)shortcut; LIMITS RPC COUNT

Check scoping

Execute local

Execute local

SetReceiving

this.OpChangeGroups(disableList.Count > 0 ? disableList.ToArray() : null, enableList.Count > 0 ? enableList.ToArray() : null); Passing a 0 sized array != passing null

SetSending

this.blockSendingGroups.Add(group); can be added to HashSet no matter if already in it

Debug.Log("OnLevelWasLoaded photonViewList.Count: " + photonViewList.Count); Exit Games internal log

this is called by Update() and in Unity that means it's single threaded.

return; No need to send OnSerialize messages (these are never buffered anyway)

Fetch all sending photonViews

continue; Only on actives

continue; Only on actives

continue; Block sending on this group

Run it trough its OnSerialize

Everything has been removed by compression, nothing to send

Debug.Log(" NO OBS on " + view.name + " " + view.owner);

Send the messages: every group is send in it's own message and unreliable and reliable are split as well

calls OnPhotonSerializeView (through ExecuteOnSerialize)

the content created here is consumed by receivers in: ReadOnSerialize

each view creates a list of values that should be sent

EVDATA:

0=View ID (an int, never compressed cause it's not in the data)

1=data of observed type (different per type of observed object)

2=compressed data (in this case, key 1 is empty)

3=list of values that are actually null (if something was changed but actually IS null)

evData[(byte)1] = dataArray; this is the actual data (script or observed object)

compress content of data set (by comparing to view.lastOnSerializeDataSent)

the "original" dataArray is NOT modified by DeltaCompressionWrite

if something was compressed, the evData key 2 and 3 are used (see above)

buffer the full data set (for next compression)

Reads updates created by OnSerializeWrite

read view ID from key (byte)0: a int-array (PUN 1.17++)

SetReceiving filtering

return; Ignore group

Skip this packet as we haven't got received complete-copy of this view yet.

store last received for delta-compression usage

obviously the owner changed and we didn't yet notice.

Compares the new data with previously sent data and skips values that didn't change.

True if anything has to be sent, false if nothing new or no data

return true; all has to be sent

We can compress as we sent a full update previously (readers can re-use previous values)

no data to be sent

if new data isn't same length as before, we send the complete data-set uncompressed

compress (by using null, instead of value, which is same as before)

compressedContent[index] is already null (initialized)

value changed, we don't replace it with null

new value is null (like a compressed value): we have to mark it so it STAYS null instead of being replaced with previous value

Only send the list of compressed fields if we actually compressed 1 or more fields.

data.Remove((byte)1); remove the original data (we only send compressed data)

all values are compressed to null, we have nothing to send

data[(byte)2] = compressedContent; current, compressted data is moved to key 2 to mark it as compressed

data[(byte)3] = valuesThatAreChangedToNull.ToArray(); data that is actually null (not just cause we didn't want to send it)

return true; some data was compressed but we need to send something

reads incoming messages created by "OnSerialize"

we have a full list of data (cause key 1 is used), so return "we have uncompressed all"

Compression was applied as data[(byte)2] exists (this is the data with some fields being compressed to null)

now we also need a previous "full" list of values to restore values that are null in this msg

return false; We dont have a full match yet, we cannot work with missing values: skip this message

despite expectation, there is no compressed data in this msg. shouldn't happen. just a null check

we replace null values in this received msg unless a index is in the "changed to null" list

data[(byte)1] = compressedContents; compressedContents are now uncompressed...

Returns true if both objects are almost identical.

Used to check whether two objects are similar enough to skip an update.

if A is not B, lets check if A is almost B

one does not equal two

Internally used to flag if the message queue was disabled by a "scene sync" situation (to re-enable it).

For automatic scene syncing, the loaded scene is put into a room property. This is the name of said prop.

Internally used to detect the current scene and load it if PhotonNetwork.automaticallySyncScene is enabled.

check if "current level" is set in props

if loaded level is not the one defined my master in props, load that level

check if "current level" is already set in props

current level is not yet in props, so this client has to set it

this.SendOutgoingCommands(); send immediately! because: in most cases the client will begin to load and not send for a while




Trò chơi Tic-Tac-Toe, game đánh caro full source code 53.608 lượt xem

Gõ tìm kiếm nhanh...